{"id":13593354,"url":"https://github.com/dimkr/tootik","last_synced_at":"2026-05-30T07:01:28.791Z","repository":{"id":154905370,"uuid":"616061427","full_name":"dimkr/tootik","owner":"dimkr","description":"A federated, text-based social network with a Gemini frontend","archived":false,"fork":false,"pushed_at":"2026-04-29T16:24:26.000Z","size":2230,"stargazers_count":200,"open_issues_count":13,"forks_count":7,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-04-29T18:23:01.360Z","etag":null,"topics":["activitypub","activitypub-server","fediverse","gemini","gemini-protocol","gemini-server","go","golang","social","social-media","social-network","sqlite"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dimkr.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-03-19T14:01:48.000Z","updated_at":"2026-04-28T05:16:02.000Z","dependencies_parsed_at":"2023-09-26T21:50:35.674Z","dependency_job_id":"91a87169-0e58-4278-9fa3-f371a3e6c7ff","html_url":"https://github.com/dimkr/tootik","commit_stats":null,"previous_names":[],"tags_count":102,"template":false,"template_full_name":null,"purl":"pkg:github/dimkr/tootik","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dimkr%2Ftootik","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dimkr%2Ftootik/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dimkr%2Ftootik/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dimkr%2Ftootik/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dimkr","download_url":"https://codeload.github.com/dimkr/tootik/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dimkr%2Ftootik/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32810395,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-08T08:22:46.396Z","status":"online","status_checked_at":"2026-05-09T02:00:06.633Z","response_time":123,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["activitypub","activitypub-server","fediverse","gemini","gemini-protocol","gemini-server","go","golang","social","social-media","social-network","sqlite"],"created_at":"2024-08-01T16:01:19.552Z","updated_at":"2026-05-09T07:07:20.948Z","avatar_url":"https://github.com/dimkr.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"# tootik\n\n[![Latest release](https://img.shields.io/github/v/release/dimkr/tootik)](https://github.com/dimkr/tootik/releases) [![Build status](https://github.com/dimkr/tootik/actions/workflows/ci.yml/badge.svg)](https://github.com/dimkr/tootik/actions) [![Go Reference](https://pkg.go.dev/badge/github.com/dimkr/tootik.svg)](https://pkg.go.dev/github.com/dimkr/tootik)\n\n![alice@pizza.example demo](https://github.com/dimkr/tootik/raw/refs/heads/pizza.example/demo.svg)\n\n## Overview\n\ntootik is a text-based social network.\n\ntootik is federated using [ActivityPub](https://www.w3.org/TR/activitypub/): users can join an existing instance or [set up](SETUP.md) their own, then interact with users on the same instance and users of [compatible servers](FEDERATION.md) like [Mastodon](https://joinmastodon.org/), [Lemmy](https://join-lemmy.org/), [Sharkey](https://activitypub.software/TransFem-org/Sharkey), [Friendica](https://friendi.ca/), [Akkoma](https://akkoma.dev/AkkomaGang/akkoma/), [GoToSocial](https://gotosocial.org/), [Mitra](https://codeberg.org/silverpill/mitra) and [PieFed](https://join.piefed.social/).\n\nUnlike other social networks, tootik doesn't have a browser-based interface or an app: instead, its minimalistic, text-based interface is served over [Gemini](https://geminiprotocol.net/):\n\n```\n                         Gemini           ActivityPub (HTTPS)\n                           →                     ⇄\n ┏━━━━━━━━━━━━━━━━━━━━━━━┓   ┏━━━━━━━━━━━━━━━━━┓   ┌─────────────────────────┐\n ┃  Bob's Gemini client  ┣━━━┫ tootik instance ┠─┬─┤ Another tootik instance │\n ┣━━━━━━━━━━━━━━━━━━━━━━━┫   ┣━━━━━━━━━━━━━━━━━┫ │ └─────────────────────────┘\n ┃2024-01-01 alice       ┃   ┃$ ./tootik ...   ┃ │ ┌────────────────┐\n ┃\u003e Hi @bob and @carol!  ┃   ┗━┳━━━━━━━━━━━━━━━┛ ├─┤ Something else │\n ┃...                    ┃     ┃                 │ └────────────────┘\n ┗━━━━━━━━━━━━━━━━━━━━━━━┛     ┃                 │ ┌───────────────────┐\n               ┏━━━━━━━━━━━━━━━┻━━━━━━━┓         └─┤ Mastodon instance ├─┐\n               ┃ Alice's Gemini client ┃           └───────────────────┘ │\n               ┣━━━━━━━━━━━━━━━━━━━━━━━┫              ┌──────────────────┴────┐\n               ┃2024-01-01 bob         ┃              │  Carol's web browser  │\n               ┃\u003e Hi @alice!           ┃              ├───────────────────────┤\n               ┃...                    ┃              │╔═╗ alice              │\n               ┗━━━━━━━━━━━━━━━━━━━━━━━┛              │╚═╝             17h ago│\n                                                      │Hi @bob and @carol!    │\n                                                      │                       │\n                                                      │  ╔═╗ bob              │\n                                                      │  ╚═╝           16h ago│\n                                                      │  Hi @alice!           │\n                                                      ├───────────────────────┤\n                                                      │┌────────────┐┌───────┐│\n                                                      ││ Hola       ││Publish││\n                                                      │└────────────┘└───────┘│\n                                                      └───────────────────────┘\n```\n\nThis makes tootik lightweight, private and accessible:\n* Its UI is [Gemini](https://geminiprotocol.net/)-based: there's a wide variety of clients to choose from and some work great on old devices.\n* Rich content is reduced to plain text and links: it's a fast, low-bandwidth UI suitable for screen readers.\n* Anonymity: you authenticate using a TLS client certificate and don't have to share your email address or real name.\n* No promoted content, tracking or analytics: social networking, with the slow and non-commercial vibe of the small internet.\n* It's a single static executable, making it easy to [set up your own instance](SETUP.md) and update it later.\n* All instance data is stored in a single file, a [sqlite](https://sqlite.org/) database that is easy to backup and restore.\n* It's lightweight: a \u003c=$5/mo VPS or a SBC is more than enough for a small instance.\n* It implements the subset of ActivityPub required for its feature set but not more, to stay small, reliable and maintainable.\n* It's written from scratch (not forked from some other project) in two languages ([Go](https://go.dev/) and SQL), making the codebase suitable for educational purposes and easy to hack on.\n* It's permissively-licensed.\n\n## Features\n\n* [Good compatibility with various fediverse servers](FEDERATION.md)\n* Text posts, with 3 privacy levels\n  * Public\n  * To followers\n  * To mentioned users\n* Sharing of public posts\n* [FEP-044f](https://codeberg.org/fediverse/fep/src/branch/main/fep/044f/fep-044f.md) quote posts, without support for approval\n* Users can follow each other to see non-public posts\n  * With support for manual approval of follow requests\n  * With support for [Mastodon's follower synchronization mechanism](https://docs.joinmastodon.org/spec/activitypub/#follower-synchronization-mechanism), aka [FEP-8fcf](https://codeberg.org/fediverse/fep/src/branch/main/fep/8fcf/fep-8fcf.md)\n* [FEP-ef61](https://codeberg.org/fediverse/fep/src/branch/main/fep/ef61/fep-ef61.md) portable accounts\n  * Accounts on different servers use one Ed25519 keypair\n  * User activity is replicated across all servers\n* Multi-choice polls\n* [Lemmy](https://join-lemmy.org/)-style communities\n  * Follow to join\n  * Mention community in a public post to start thread\n  * Community sends posts and replies to all members\n* Bookmarks\n* Full-text search within posts\n* Upload of posts and user avatars, over [Titan](gemini://transjovian.org/titan)\n* Automatic deletion of old posts\n* Account migration, in both directions\n* Support for multiple client certificates\n* Support for invite-only registration\n\n## Using tootik\n\nYou can join an [existing instance](gemini://didkey.000090000.xyz) or [set up your own](SETUP.md).\n\n## Interactive Demo\n\n\tgo generate ./migrations\n\tgo run -tags fts5 ./cmd/demo\n\n## Database Drivers\n\ntootik supports three `database/sql` drivers:\n* [`github.com/mattn/go-sqlite3`](https://github.com/mattn/go-sqlite3), enabled when `CGO_ENABLED=1` or `-tags sqlite_mattn`\n* [`modernc.org/sqlite`](https://gitlab.com/cznic/sqlite), enabled when `CGO_ENABLED=0` or `-tags sqlite_modernc`\n* [`github.com/ncruces/go-sqlite3/driver`](https://github.com/ncruces/go-sqlite3), enabled when `-tags sqlite_ncruces`\n\n## Building\n\n\tgo generate ./migrations\n\tgo build ./cmd/tootik -tags fts5\n\nTo build a static executable:\n\n\tgo generate ./migrations\n\tCGO_ENABLED=0 go build ./cmd/tootik\n\nTo build a smaller executable without support for profiling, add the `no_pprof` build tag:\n\n\tCGO_ENABLED=0 go build -tags no_pprof ./cmd/tootik\n\n## Architecture\n\n```\n┏━━━━━━━┓ ┏━━━━━━━━┓ ┏━━━━━━━━━┓ ┏━━━━━━━━━┓\n┃ notes ┃ ┃ shares ┃ ┃ persons ┃ ┃ follows ┃\n┣━━━━━━━┫ ┣━━━━━━━━┫ ┣━━━━━━━━━┫ ┣━━━━━━━━━┫\n┃object ┃ ┃note    ┃ ┃actor    ┃ ┃follower ┃\n┃author ┃ ┃by      ┃ ┃...      ┃ ┃followed ┃\n┃...    ┃ ┃...     ┃ ┃         ┃ ┃...      ┃\n┗━━━━━━━┛ ┗━━━━━━━━┛ ┗━━━━━━━━━┛ ┗━━━━━━━━━┛\n```\n\nMost user-visible data is stored in 4 tables in tootik's database:\n1. `notes`, which contains [Object](https://pkg.go.dev/github.com/dimkr/tootik/ap#Object) objects that represent posts\n2. `shares`, which records \"user A shared post B\" relationships\n3. `persons`, which contains [Actor](https://pkg.go.dev/github.com/dimkr/tootik/ap#Actor) objects that represent users\n4. `follows`, which records \"user A follows user B\" relationships\n\n`notes.author`, `shares.by`, `follows.follower` and `follows.followed` point to rows in `persons`.\n\n`shares.note` points to a row in `notes`.\n\n```\n┌───────┐ ┌────────┐ ┌─────────┐ ┌─────────┐ ┏━━━━━━━━┓ ┏━━━━━━━━┓\n│ notes │ │ shares │ │ persons │ │ follows │ ┃ outbox ┃ ┃ inbox  ┃\n├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ┣━━━━━━━━┫ ┣━━━━━━━━┫\n│object │ │note    │ │actor    │ │follower │ ┃activity┃ ┃activity┃\n│author │ │by      │ │...      │ │followed │ ┃sender  ┃ ┃sender  ┃\n│...    │ │...     │ │         │ │...      │ ┃...     ┃ ┃...     ┃\n└───────┘ └────────┘ └─────────┘ └─────────┘ ┗━━━━━━━━┛ ┗━━━━━━━━┛\n```\n\nFederation happens through two tables, `inbox` and `outbox`. Both contain [Activity](https://pkg.go.dev/github.com/dimkr/tootik/ap#Activity) objects that represent actions performed by the users in `persons`.\n\n`inbox` contains activities by users on other servers, while `outbox` contains activities of local users.\n\n```\n  ┏━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━━┓\n  ┃ gmi.Wrap ┣━┫ gemini.Listener ┃\n  ┗━━━━━━━━━━┛ ┗━━━━━━━━┳━━━━━━━━┛\n                ┏━━━━━━━┻━━━━━━━━━━━┓\n                ┃   front.Handler   ┃\n                ┗━━━━━━━━━┳━━━━━━━━━┛\n┌───────┐ ┌────────┐ ┌────┸────┐ ┌─────────┐ ┌────────┐ ┌────────┐\n│ notes │ │ shares │ │ persons │ │ follows │ │ outbox │ │ inbox  │\n├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ├────────┤ ├────────┤\n│object │ │note    │ │actor    │ │follower │ │activity│ │activity│\n│author │ │by      │ │...      │ │followed │ │sender  │ │sender  │\n│...    │ │...     │ │         │ │...      │ │...     │ │...     │\n└───────┘ └────────┘ └─────────┘ └─────────┘ └────────┘ └────────┘\n```\n\n[gemini.Listener](https://pkg.go.dev/github.com/dimkr/tootik/front/gemini#Listener) is a Gemini server that handles requests using [Handler](https://pkg.go.dev/github.com/dimkr/tootik/front#Handler). It adds rows to `persons` during new user registration and changes rows when users change properties like their display name.\n\n[gemini.Listener](https://pkg.go.dev/github.com/dimkr/tootik/front/gemini#Listener) provides [Handler](https://pkg.go.dev/github.com/dimkr/tootik/front#Handler) with a [writer](https://pkg.go.dev/github.com/dimkr/tootik/front/text/gmi#Wrap) that builds a Gemini response and asynchronously sends it to the client in chunks, while [Handler](https://pkg.go.dev/github.com/dimkr/tootik/front#Handler) continues to handle the request and append more lines to the page.\n\n```\n  ┌──────────┐ ┌─────────────────┐\n  │ gmi.Wrap ├─┤ gemini.Listener │\n  └──────────┘ └────────┬────────┘\n                ┌───────┴───────────┐\n    ┏━━━━━━━━━━━┥   front.Handler   │\n    ┃           └┰────────┬───────┰─┘\n┌───┸───┐ ┌──────┸─┐ ┌────┴────┐ ┌┸────────┐ ┌────────┐ ┌────────┐\n│ notes │ │ shares │ │ persons │ │ follows │ │ outbox │ │ inbox  │\n├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ├────────┤ ├────────┤\n│object │ │note    │ │actor    │ │follower │ │activity│ │activity│\n│author │ │by      │ │...      │ │followed │ │sender  │ │sender  │\n│...    │ │...     │ │         │ │...      │ │...     │ │...     │\n└───────┘ └────────┘ └─────────┘ └─────────┘ └────────┘ └────────┘\n```\n\nIn addition, Gemini requests can:\n* Add rows to `notes` (new post)\n* Change rows in `notes` (post editing)\n* Add rows to `shares` (user shares a post)\n* Remove rows from `shares` (user no longer shares a post)\n* Add rows to `follows` (user A followed user B)\n* Remove rows from `follows` (user A unfollowed user B)\n* ...\n\n```\n  ┌──────────┐ ┌─────────────────┐\n  │ gmi.Wrap ├─┤ gemini.Listener │\n  └──────────┘ └────────┬────────┘\n                ┌───────┴───────────┐\n    ┌───────────┤   front.Handler   ┝━━━━━━━━━━┓\n    │           └┬────────┬───────┬─┘          ┃\n┌───┴───┐ ┌──────┴─┐ ┌────┴────┐ ┌┴────────┐ ┌─┸──────┐ ┌────────┐\n│ notes │ │ shares │ │ persons │ │ follows │ │ outbox │ │ inbox  │\n├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ├────────┤ ├────────┤\n│object │ │note    │ │actor    │ │follower │ │activity│ │activity│\n│author │ │by      │ │...      │ │followed │ │sender  │ │sender  │\n│...    │ │...     │ │         │ │...      │ │...     │ │...     │\n└───────┘ └────────┘ └─────────┘ └─────────┘ └────────┘ └────────┘\n```\n\nUser actions like post creation or deletion are recorded as [Activity](https://pkg.go.dev/github.com/dimkr/tootik/ap#Activity) objects written to `outbox`.\n\n```\n                                      ┏━━━━━━━━━━━━━━━┓\n  ┌──────────┐ ┌─────────────────┐    ┃ outbox.Mover  ┃\n  │ gmi.Wrap ├─┤ gemini.Listener │    ┃ outbox.Poller ┃\n  └──────────┘ └────────┬────────┘    ┃ fed.Syncer    ┃\n                ┌───────┴───────────┐ ┗━━━┳━━━━━┳━━━━━┛\n    ┌───────────┤   front.Handler   ├─────╂────┐┃\n    │           └┬────────┬───────┬─┘     ┃    │┃\n┌───┴───┐ ┌──────┴─┐ ┌────┴────┐ ┌┴───────┸┐ ┌─┴┸─────┐ ┌────────┐\n│ notes │ │ shares │ │ persons │ │ follows │ │ outbox │ │ inbox  │\n├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ├────────┤ ├────────┤\n│object │ │note    │ │actor    │ │follower │ │activity│ │activity│\n│author │ │by      │ │...      │ │followed │ │sender  │ │sender  │\n│...    │ │...     │ │         │ │...      │ │...     │ │...     │\n└───────┘ └────────┘ └─────────┘ └─────────┘ └────────┘ └────────┘\n```\n\ntootik may perform automatic actions and push additional activities to `outbox`, on behalf of the user:\n1. Follow the new account and unfollow the old one, if a followed user moved their account\n2. Update poll results for polls published by the user, and send the new results to followers\n3. Handle disagreement between `follows` rows for this user and what other servers know\n\n```\n                                      ┌───────────────┐\n  ┌──────────┐ ┌─────────────────┐    │ outbox.Mover  │\n  │ gmi.Wrap ├─┤ gemini.Listener │    │ outbox.Poller │\n  └──────────┘ └────────┬────────┘    │ fed.Syncer    │\n                ┌───────┴───────────┐ └───┬─────┬─────┘\n    ┌───────────┤   front.Handler   ├─────┼────┐│\n    │           └┬────────┬───────┬─┘     │    ││\n┌───┴───┐ ┌──────┴─┐ ┌────┴────┐ ┌┴───────┴┐ ┌─┴┴─────┐ ┌────────┐\n│ notes │ │ shares │ │ persons │ │ follows │ │ outbox │ │ inbox  │\n├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ├────────┤ ├────────┤\n│object │ │note    │ │actor    │ │follower │ │activity│ │activity│\n│author │ │by      │ │...      │ │followed │ │sender  │ │sender  │\n│...    │ │...     │ │         │ │...      │ │...     │ │...     │\n└───────┘ └────────┘ └─────────┘ └───────┰─┘ └┰───────┘ └────────┘\n                                        ┏┻━━━━┻━━━━━┓\n                                        ┃ fed.Queue ┃\n                                        ┗━━━━━━━━━━━┛\n```\n\n[fed.Queue](https://pkg.go.dev/github.com/dimkr/tootik/fed#Queue) polls `outbox` and delivers these activities to followers on other servers. It uses the `deliveries` table to track delivery progress and retry failed deliveries.\n\n```\n                                      ┌───────────────┐\n  ┌──────────┐ ┌─────────────────┐    │ outbox.Mover  │\n  │ gmi.Wrap ├─┤ gemini.Listener │    │ outbox.Poller │\n  └──────────┘ └────────┬────────┘    │ fed.Syncer    │\n                ┌───────┴───────────┐ └───┬─────┬─────┘\n    ┌───────────┤   front.Handler   ├─────┼────┐│\n    │           └┬────────┬───────┬─┘     │    ││\n┌───┴───┐ ┌──────┴─┐ ┌────┴────┐ ┌┴───────┴┐ ┌─┴┴─────┐ ┌────────┐\n│ notes │ │ shares │ │ persons │ │ follows │ │ outbox │ │ inbox  │\n├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ├────────┤ ├────────┤\n│object │ │note    │ │actor    │ │follower │ │activity│ │activity│\n│author │ │by      │ │...      │ │followed │ │sender  │ │sender  │\n│...    │ │...     │ │         │ │...      │ │...     │ │...     │\n└───────┘ └────────┘ └────┰────┘ └───────┬─┘ └┬───────┘ └────────┘\n                          ┃             ┌┴────┴─────┐\n                  ┏━━━━━━━┻━━━━━━┓ ┏━━━━┥ fed.Queue │\n                  ┃ fed.Resolver ┣━┛    └───────────┘\n                  ┗━━━━━━━━━━━━━━┛\n```\n\n[Resolver](https://pkg.go.dev/github.com/dimkr/tootik/fed#Resolver) is responsible for fetching [Actor](https://pkg.go.dev/github.com/dimkr/tootik/ap#Actor)s that represent users of other servers, using an ID or a `user@domain` pair and [WebFinger](https://datatracker.ietf.org/doc/html/rfc7033). The fetched objects are cached in `persons`, and contain properties like the user's inbox URL and public key.\n\n[fed.Queue](https://pkg.go.dev/github.com/dimkr/tootik/fed#Queue) uses [Resolver](https://pkg.go.dev/github.com/dimkr/tootik/fed#Resolver) to make a list of unique inbox URLs each activity should be delivered to. If this is a wide delivery (a public post or a post to followers) and multiple recipients reside on the same server, [fed.Queue](https://pkg.go.dev/github.com/dimkr/tootik/fed#Queue) delivers the activity to this server only once.\n\n```\n                                      ┌───────────────┐\n  ┌──────────┐ ┌─────────────────┐    │ outbox.Mover  │\n  │ gmi.Wrap ├─┤ gemini.Listener │    │ outbox.Poller │\n  └──────────┘ └────────┬────────┘    │ fed.Syncer    │\n                ┌───────┴───────────┐ └───┬─────┬─────┘ ┏━━━━━━━━━━━━━━┓\n    ┌───────────┤   front.Handler   ├─────┼────┐│    ┏━━┫ fed.Listener ┣━━━━━━┓\n    │           └┬────────┬───────┬─┘     │    ││    ┃  ┗━━━━━┳━━━━━━━━┛      ┃\n┌───┴───┐ ┌──────┴─┐ ┌────┴────┐ ┌┴───────┴┐ ┌─┴┴────┸┐ ┌─────┸──┐            ┃\n│ notes │ │ shares │ │ persons │ │ follows │ │ outbox │ │ inbox  │            ┃\n├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ├────────┤ ├────────┤            ┃\n│object │ │note    │ │actor    │ │follower │ │activity│ │activity│            ┃\n│author │ │by      │ │...      │ │followed │ │sender  │ │sender  │            ┃\n│...    │ │...     │ │         │ │...      │ │...     │ │...     │            ┃\n└───────┘ └────────┘ └────┬────┘ └───────┬─┘ └┬───────┘ └────────┘            ┃\n                          │             ┌┴────┴─────┐                         ┃\n                  ┌───────┴──────┐ ┌────┤ fed.Queue │                         ┃\n                  │ fed.Resolver ├─┘    └───────────┘                         ┃\n                  └───────┰──────┘                                            ┃\n                          ┃                                                   ┃\n                          ┃                                                   ┃\n                          ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n```\n\nRequests from other servers are handled by [fed.Listener](https://pkg.go.dev/github.com/dimkr/tootik/fed#Listener), a HTTP server.\n\nIt extracts the signature and key ID from a request using [httpsig.Extract](https://pkg.go.dev/github.com/dimkr/tootik/httpsig#Extract), uses [Resolver](https://pkg.go.dev/github.com/dimkr/tootik/fed#Resolver) to fetch the public key if needed, validates the request using [Verify](https://pkg.go.dev/github.com/dimkr/tootik/httpsig#Signature.Verify) and inserts the received [Activity](https://pkg.go.dev/github.com/dimkr/tootik/ap#Activity) object into `inbox`.\n\n```\n                                      ┌───────────────┐\n  ┌──────────┐ ┌─────────────────┐    │ outbox.Mover  │\n  │ gmi.Wrap ├─┤ gemini.Listener │    │ outbox.Poller │\n  └──────────┘ └────────┬────────┘    │ fed.Syncer    │\n                ┌───────┴───────────┐ └───┬─────┬─────┘ ┌──────────────┐\n    ┌───────────┤   front.Handler   ├─────┼────┐│    ┌──┤ fed.Listener ├──────┐\n    │           └┬────────┬───────┬─┘     │    ││    │  └─────┬────────┘      │\n┌───┴───┐ ┌──────┴─┐ ┌────┴────┐ ┌┴───────┴┐ ┌─┴┴────┴┐ ┌─────┴──┐            │\n│ notes │ │ shares │ │ persons │ │ follows │ │ outbox │ │ inbox  │            │\n├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ├────────┤ ├────────┤            │\n│object │ │note    │ │actor    │ │follower │ │activity│ │activity│            │\n│author │ │by      │ │...      │ │followed │ │sender  │ │sender  │            │\n│...    │ │...     │ │         │ │...      │ │...     │ │...     │            │\n└───┰───┘ └───┰────┘ └────┬────┘ └────┰──┬─┘ └┬───────┘ └──────┰─┘            │\n    ┃         ┃           │           ┃ ┌┴────┴─────┐     ┏━━━━┻━━━━━━━━┓     │\n    ┃         ┃   ┌───────┴──────┐ ┌──╂─┤ fed.Queue │     ┃ inbox.Queue ┃     │\n    ┃         ┃   │ fed.Resolver ├─┘  ┃ └───────────┘     ┗━┳━┳━┳━━━━━━━┛     │\n    ┃         ┃   └───────┬──────┘    ┗━━━━━━━━━━━━━━━━━━━━━┛ ┃ ┃             │\n    ┃         ┗━━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃             │\n    ┗━━━━━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛             │\n                          └───────────────────────────────────────────────────┘\n```\n\nOnce inserted into `inbox`, [inbox.Queue](https://pkg.go.dev/github.com/dimkr/tootik/inbox#Queue) processes the received activities:\n* Adds new posts received in `Create` activities to `notes`\n* Edits posts in `notes` according to `Update` activities\n* Records `Announce` activities in `shares`\n* Marks a follower-followed relationship in `follows` as accepted, when the followed user sends an `Accept` activity\n* Adds a new row to `follows` when a remote user sends a `Follow` activity to a local user\n* ...\n\n```\n                                      ┌───────────────┐\n  ┌──────────┐ ┌─────────────────┐    │ outbox.Mover  │\n  │ gmi.Wrap ├─┤ gemini.Listener │    │ outbox.Poller │\n  └──────────┘ └────────┬────────┘    │ fed.Syncer    │\n                ┌───────┴───────────┐ └───┬─────┬─────┘ ┌──────────────┐\n    ┌───────────┤   front.Handler   ├─────┼────┐│    ┌──┤ fed.Listener ├──────┐\n    │           └┬────────┬───────┬─┘     │    ││    │  └─────┬────────┘      │\n┌───┴───┐ ┌──────┴─┐ ┌────┴────┐ ┌┴───────┴┐ ┌─┴┴────┴┐ ┌─────┴──┐            │\n│ notes │ │ shares │ │ persons │ │ follows │ │ outbox │ │ inbox  │            │\n├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ├────────┤ ├────────┤            │\n│object │ │note    │ │actor    │ │follower │ │activity│ │activity│            │\n│author │ │by      │ │...      │ │followed │ │sender  │ │sender  │            │\n│...    │ │...     │ │         │ │...      │ │...     │ │...     │            │\n└───┬───┘ └───┬────┘ └────┬────┘ └────┬──┬─┘ └┬──────┰┘ └──────┬─┘            │\n    │         │           │           │ ┌┴────┴─────┐┃    ┌────┴────────┐     │\n    │         │   ┌───────┴──────┐ ┌──┼─┤ fed.Queue │┗━━━━┥ inbox.Queue │     │\n    │         │   │ fed.Resolver ├─┘  │ └───────────┘     └─┬─┬─┬───────┘     │\n    │         │   └───────┬──────┘    └─────────────────────┘ │ │             │\n    │         └───────────┼───────────────────────────────────┘ │             │\n    └─────────────────────┼─────────────────────────────────────┘             │\n                          └───────────────────────────────────────────────────┘\n```\n\nSometimes, a received or newly created local [Activity](https://pkg.go.dev/github.com/dimkr/tootik/ap#Activity) is forwarded to the followers of a local user:\n* When a remote user replies in a thread started by a local user, the received [Activity](https://pkg.go.dev/github.com/dimkr/tootik/ap#Activity) is inserted into `outbox` and forwarded to all followers of the local user.\n* When a user creates a new post, edits a post or deletes a post in a local community, the [Activity](https://pkg.go.dev/github.com/dimkr/tootik/ap#Activity) is inserted into `outbox` and forwarded to all community members.\n\n```\n                                      ┌───────────────┐\n  ┌──────────┐ ┌─────────────────┐    │ outbox.Mover  │\n  │ gmi.Wrap ├─┤ gemini.Listener │    │ outbox.Poller │\n  └──────────┘ └────────┬────────┘    │ fed.Syncer    │\n                ┌───────┴───────────┐ └───┬─────┬─────┘ ┌──────────────┐\n    ┌───────────┤   front.Handler   ├─────┼────┐│    ┌──┤ fed.Listener ├──────┐\n    │           └┬────────┬───────┬─┘     │    ││    │  └─────┬────────┘      │\n┌───┴───┐ ┌──────┴─┐ ┌────┴────┐ ┌┴───────┴┐ ┌─┴┴────┴┐ ┌─────┴──┐            │\n│ notes │ │ shares │ │ persons │ │ follows │ │ outbox │ │ inbox  │            │\n├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ├────────┤ ├────────┤            │\n│object │ │note    │ │actor    │ │follower │ │activity│ │activity│            │\n│author │ │by      │ │...      │ │followed │ │sender  │ │sender  │            │\n│...    │ │...     │ │         │ │...      │ │...     │ │...     │            │\n└───┬───┘ └───┬────┘ └────┬────┘ └────┬──┬─┘ └┬──────┬┘ └──────┬─┘            │\n    │         │           │           │ ┌┴────┴─────┐│    ┌────┴────────┐     │\n    │         │   ┌───────┴──────┐ ┌──┼─┤ fed.Queue │└────┤ inbox.Queue │     │\n    │         │   │ fed.Resolver ├─┘  │ └───────────┘     └─┬─┬─┬─┰─────┘     │\n    │         │   └───────┬─┰────┘    └─────────────────────┘ │ │ ┃           │\n    │         └───────────┼─╂─────────────────────────────────┘ │ ┃           │\n    └─────────────────────┼─╂───────────────────────────────────┘ ┃           │\n                          └─╂─────────────────────────────────────╂───────────┘\n                            ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n```\n\nTo display details like the user's name and speed up the verification of future incoming replies, [inbox.Queue](https://pkg.go.dev/github.com/dimkr/tootik/inbox#Queue) uses [Resolver](https://pkg.go.dev/github.com/dimkr/tootik/fed#Resolver) to fetch the [Actor](https://pkg.go.dev/github.com/dimkr/tootik/ap#Actor) objects of mentioned users (if needed).\n\n```\n                                   ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n                                   ┃  ┌───────────────┐                   ┃\n  ┌──────────┐ ┌─────────────────┐ ┃  │ outbox.Mover  │                   ┃\n  │ gmi.Wrap ├─┤ gemini.Listener │ ┃  │ outbox.Poller │                   ┃\n  └──────────┘ └────────┬────────┘ ┃  │ fed.Syncer    │                   ┃\n                ┌───────┴──────────┸┐ └───┬─────┬─────┘ ┌──────────────┐  ┃\n    ┌───────────┤   front.Handler   ├─────┼────┐│    ┌──┤ fed.Listener ├──╂───┐\n    │           └┬────────┬───────┬─┘     │    ││    │  └─────┬────────┘  ┃   │\n┌───┴───┐ ┌──────┴─┐ ┌────┴────┐ ┌┴───────┴┐ ┌─┴┴────┴┐ ┌─────┴──┐ ┏━━━━━━┻━┓ │\n│ notes │ │ shares │ │ persons │ │ follows │ │ outbox │ │ inbox  │ ┃  feed  ┃ │\n├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ├────────┤ ├────────┤ ┣━━━━━━━━┫ │\n│object │ │note    │ │actor    │ │follower │ │activity│ │activity│ ┃follower┃ │\n│author │ │by      │ │...      │ │followed │ │sender  │ │sender  │ ┃note    ┃ │\n│...    │ │...     │ │         │ │...      │ │...     │ │...     │ ┃...     ┃ │\n└─┰─┬───┘ └─┰─┬────┘ └──┰─┬────┘ └──┰─┬──┬─┘ └┬──────┬┘ └──────┬─┘ ┗━━━━━━┳━┛ │\n  ┃ │       ┃ │ ┏━━━━━━━┛ │         ┃ │ ┌┴────┴─────┐│    ┌────┴────────┐ ┃   │\n  ┃ │       ┃ │ ┃ ┌───────┴──────┐ ┌╂─┼─┤ fed.Queue │└────┤ inbox.Queue │ ┃   │\n  ┃ │       ┃ │ ┃ │ fed.Resolver ├─┘┃ │ └───────────┘     └─┬─┬─┬─┬─────┘ ┃   │\n  ┃ │       ┃ │ ┃ └───────┬─┬────┘  ┃ └─────────────────────┘ │ │ │       ┃   │\n  ┃ │       ┃ └─╂─────────┼─┼───────╂─────────────────────────┘ │ │       ┃   │\n  ┃ └───────╂───╂─────────┼─┼───────╂───────────────────────────┘ │       ┃   │\n  ┃         ┃   ┃         └─┼───────╂─────────────────────────────┼───────╂───┘\n┏━┻━━━━━━━━━┻━━━┻━━━┓       └───────╂─────────────────────────────┘       ┃\n┃ inbox.FeedUpdater ┣━━━━━━━━━━━━━━━┛                                     ┃\n┗━━━━━━━━━┳━━━━━━━━━┛                                                     ┃\n          ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n```\n\nTo speed up each user's feed, [inbox.FeedUpdater](https://pkg.go.dev/github.com/dimkr/tootik/inbox#FeedUpdater) periodically appends rows to the `feed` table. This table holds all information that appears in the user's feed: posts written or shared by followed users, author information and more, eliminating the need for `join` queries, slow filtering by post visibility, deduplication and sorting by time when a user views their feed. This table is indexed by user and time, allowing fast querying of a single feed page for a particular user.\n\n```\n                                      ┏━━━━━━━━━━━━━━━━┓\n                        ┏━━━━━━━━━━━━━┫ cluster.Server ┣━━━━━━┓\n                        ┃             ┗━━━━━━━━━━━━━━━━┛      ┃\n                        ┃          ┌──────────────────────────╂───────────┐\n                        ┃          │  ┌───────────────┐       ┃           │\n  ┌──────────┐ ┌────────┸────────┐ │  │ outbox.Mover  │       ┃           │\n  │ gmi.Wrap ├─┤ gemini.Listener │ │  │ outbox.Poller │       ┃           │\n  └──────────┘ └────────┬────────┘ │  │ fed.Syncer    │       ┃           │\n                ┌───────┴──────────┴┐ └───┬─────┬─────┘ ┌─────┸────────┐  │\n    ┌───────────┤   front.Handler   ├─────┼────┐│    ┌──┤ fed.Listener ├──┼───┐\n    │           └┬────────┬───────┬─┘     │    ││    │  └─────┬────────┘  │   │\n┌───┴───┐ ┌──────┴─┐ ┌────┴────┐ ┌┴───────┴┐ ┌─┴┴────┴┐ ┌─────┴──┐ ┌──────┴─┐ │\n│ notes │ │ shares │ │ persons │ │ follows │ │ outbox │ │ inbox  │ │  feed  │ │\n├───────┤ ├────────┤ ├─────────┤ ├─────────┤ ├────────┤ ├────────┤ ├────────┤ │\n│object │ │note    │ │actor    │ │follower │ │activity│ │activity│ │follower│ │\n│author │ │by      │ │...      │ │followed │ │sender  │ │sender  │ │note    │ │\n│...    │ │...     │ │         │ │...      │ │...     │ │...     │ │...     │ │\n└─┬─┬───┘ └─┬─┬────┘ └──┬─┬────┘ └──┬─┬──┬─┘ └┬──────┬┘ └──────┬─┘ └──────┬─┘ │\n  │ │       │ │ ┌───────┘ │         │ │ ┌┴────┴─────┐│    ┌────┴────────┐ │   │\n  │ │       │ │ │ ┌───────┴──────┐ ┌┼─┼─┤ fed.Queue │└────┤ inbox.Queue │ │   │\n  │ │       │ │ │ │ fed.Resolver ├─┘│ │ └───────────┘     └─┬─┬─┬─┬─────┘ │   │\n  │ │       │ │ │ └─────┰─┬─┬────┘  │ └─────────────────────┘ │ │ │       │   │\n  │ │       │ └─┼───────╂─┼─┼───────┼─────────────────────────┘ │ │       │   │\n  │ └───────┼───┼───────╂─┼─┼───────┼───────────────────────────┘ │       │   │\n  │         │   │       ┃ └─┼───────┼─────────────────────────────┼───────┼───┘\n┌─┴─────────┴───┴───┐   ┃   └───────┼─────────────────────────────┘       │\n│ inbox.FeedUpdater ├───╂───────────┘                                     │\n└─────────┬─────────┘   ┃                                                 │\n          └─────────────╂─────────────────────────────────────────────────┘\n               ┏━━━━━━━━┻━━━━━━━┓\n               ┃ cluster.Client ┃\n               ┗━━━━━━━━━━━━━━━━┛\n```\n\nThe [cluster](https://pkg.go.dev/github.com/dimkr/tootik/cluster) package contains complex tests that simulate interaction between users on multiple servers. These tests are easy to write, they're fast and they run in parallel without affecting each other.\n\nThe tests use three main constructs: [Client](https://pkg.go.dev/github.com/dimkr/tootik/cluster#Client), [Server](https://pkg.go.dev/github.com/dimkr/tootik/cluster#Server) and [Cluster](https://pkg.go.dev/github.com/dimkr/tootik/cluster#Cluster).\n\nDuring tests, all [http.Request](https://pkg.go.dev/net/http#Request)s sent by tootik (like those sent by [fed.Resolver](https://pkg.go.dev/github.com/dimkr/tootik/fed#Resolver)) are sent through [Client](https://pkg.go.dev/github.com/dimkr/tootik/cluster#Client).\n\n[Server](https://pkg.go.dev/github.com/dimkr/tootik/cluster#Server) handles all kinds of incoming requests:\n* It uses a Unix socket wrapped with TLS and [gemini.Listener](https://pkg.go.dev/github.com/dimkr/tootik/front/gemini#Listener) to allow tests to simulate interaction with the [Gemini](https://geminiprotocol.net/) interface, including user authentication through client certificates\n* It uses the same [http.Handler](https://pkg.go.dev/net/http#Handler) as [fed.Listener](https://pkg.go.dev/github.com/dimkr/tootik/front/fed#Listener) to handle an incoming [http.Request](https://pkg.go.dev/net/http#Request) but without needing an actual HTTP server\n\n[Client](https://pkg.go.dev/github.com/dimkr/tootik/cluster#Client) holds a mapping between domain names and [Server](https://pkg.go.dev/github.com/dimkr/tootik/cluster#Server)s: it allows these servers to talk to each other by passing the [http.Request](https://pkg.go.dev/net/http#Request) sent by one server to the [http.Handler](https://pkg.go.dev/net/http#Handler) of another.\n\n[Cluster](https://pkg.go.dev/github.com/dimkr/tootik/cluster#Cluster) is a high-level wrapper for easy creation of multiple [Server](https://pkg.go.dev/github.com/dimkr/tootik/cluster#Server)s capable of federating with each other, given a list of domain names.\n\n## More Documentation\n\n* [Setup guide](SETUP.md)\n* [Frontend](front/README.md)\n* [Migrations](migrations/README.md)\n* [Compatibility](FEDERATION.md)\n\n## Credits and Legal Information\n\ntootik is free and unencumbered software released under the terms of the [Apache License Version 2.0](https://www.apache.org/licenses/LICENSE-2.0); see LICENSE for the license text.\n\nThe ASCII art logo in `front/logo.go` was made using [FIGlet](http://www.figlet.org/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdimkr%2Ftootik","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdimkr%2Ftootik","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdimkr%2Ftootik/lists"}