ktistec
Ktistec is an ActivityPub server. It is intended for individual users, not communities of users, although support for additional users will be added in the future. It is designed to have few runtime dependencies -- for example, it uses SQLite as its database, instead of PostgreSQL + Redis + etc. It is licensed under the AGPLv3.
Ktistec powers Epiktistes, my low-volume home in the Fediverse. If you want to talk to me, I'm @toddsundsted@epiktistes.com.
Features
Ktistec is intended for writing and scripting.
Text and images
Text formatting options include bold, italic, strikethrough, code (inline and block), superscript/subscript, headers, blockquotes with nested indentation, and both bullet and numeric lists.
Ktistec supports inline placement of images, with ActivityPub image attachments used for compatibility with non-Ktistec servers.
Draft posts
Meaningful writing is an iterative process so Ktistec supports draft posts. Draft posts aren't visible until you publish them.
Threaded replies
Threaded replies make it easier to follow discussions with many posts.
@-mention and #-hashtag autocomplete
Ktistec automatically converts @-mentions and #-hashtags into links, and to encourage hands-on-the-keyboard composition, Ktistec supports autocompletion.
Control over comment visibility
Ktistec promotes healthy dialog. Ktistec allows you to control which replies to your posts are public and visible to anonymous users, and which are private.
Pretty URLs
Assign pretty (canonical) URLs to posts, both for SEO and as helpful mnemonics for users (and yourself).
Followers/following
The Fediverse is a distributed social network. You can follow other users on other servers from your timeline or by searching for them by name. Ktistec is also compatible with the "remote follow" protocol used by Mastodon and other servers.
Content discovery
In addition to following other users, you can follow threads, hashtags and even mentions. When posts arrive for content you follow, a notification is added to your notifications. Because running a single user instance can be lonely, Ktistec also proactively (and gently) fetches relevant content from other servers.
To make navigation and discovery easier, post details pages now have labels with links to internal hashtag and mention index pages.
Content filtering
Content filters prevent undesirable content from appearing in your timeline and notifications. Filter terms match on the text of a post (ignoring any markup). Filters support wildcards.
Blocking
Ktistec gives you control over what you see. Blocking authors removes them and their posts from your timeline. Blocking posts removes individual posts.
Metrics
Ktistec tracks metrics about how the instance is performing. Metrics include inbox and outbox activity, as well as memory usage; and the machinery is in place to do much more.
Tasks
View currently running tasks. Tasks, in Ktistec, are background jobs that deliver content, fetch content, and perform other housekeeping chores.
Scripts
The Ktistec server will periodically and in sequential order run all
executable files in the etc/scripts
directory.
When running scripts, the Ktistec server will set the following variables in the child process's environment:
- API_KEY - authenticates the script with the server
- KTISTEC_HOST - the base URL of the server instance
- KTISTEC_NAME - the name of the server instance
- USERNAME - the username of the account the script is run as
Output to STDOUT
and STDERR
will show up in the Ktistec server
logs. Output to STDOUT
will be emitted as severity INFO
and
output to STDERR
will be emitted as severity WARN
.
Scripts can use the API to communicate with the Ktistec server.
API
Given a valid API_KEY
, scripts and external tools have full access
to the Ktistec server's API. This allows them to automate actions on
the server such as posting, following, sharing, liking, etc.
The table below contains a list of supported endpoints:
| Method | Path | Notes |
| GET | /actors/:username/inbox | Retrieves a page of activities
in your inbox as an ActivityPub collection. |
| GET | /actors/:username/outbox | Retrieves a page of activities
in your outbox as an ActivityPub collection. |
| POST | /actors/:username/outbox | Puts an activity
in your outbox for delivery. |
| GET | /actors/:username/posts | Retrieves a page of objects
you've published. |
| GET | /actors/:username/drafts | Retrieves a page of objects
you've saved as drafts. |
| GET | /actors/:username/followers | Retrieves a page of actors
following you. |
| GET | /actors/:username/following | Retrieves a page of actors
you're following. |
| GET | /lookup/activity?iri=:iri | Looks up the activity
in the server cache identified by iri
. |
| GET | /lookup/actor?iri=:iri | Looks up the actor
in the server cache identified by iri
. |
| GET | /lookup/object?iri=:iri | Looks up the object
in the server cache identified by iri
. |
| GET | /sessions | Gets a representation of an authentication attempt with unset username
and password
. |
| POST | /sessions | Attempts authentication using the supplied username
and password
. |
| DELETE | /sessions | Destroys the current session. |
The /sessions
endpoint is useful when doing script development
outside of the server environment. The scripts the Ktistec server
runs directly do not need to create their own session -- the supplied
API_KEY
is sufficient.
A Note on ActivityPub
At its core, Ktistec is an ActivityPub server and some things about its API make more sense if you understand a little bit about ActivityPub.
ActivityPub is a protocol for sending and receiving messages.
Entities called actors
send and receive activities
. Activities
represent the various social actions that define interaction on the
Fediverse: posting, sharing and liking posts, following other actors,
etc.
Every actor
has an inbox
for receiving activities
from the
actors
they follow, and an outbox
for sending activities
to the
actors
that follow them.
A Fediverse "post", "status", "note", "toot", etc. are all names for
an ActivityPub object
. ActivityPub activities
are typically about
objects
.
ActivityPub actors
, activities
, and objects
are described using
a flavor of JSON known as JSON-LD. Every actor
, activity
, and
object
is identified by a unique IRI
.
Ktistec allows a JSON shorthand (described below) to be used when publishing, which greatly simplifies the development of scripts.
Publishing an Object
To publish an object
, POST
a JSON activity
to the outbox
endpoint (examples assume scripts use curl
). The JSON activity
may
include the following fields:
| Name | Notes |
| type | Must be "Publish". |
| content | An HTML formatted string. |
| name | Optional. A plain text string often displayed as the title of a post. |
| summary | Optional. A plain text string often used as a summary of a longer post. |
| object | Optional. The IRI of the object
being updated (instead of created). |
| in-reply-to | Optional. The IRI of the object
being replied to. |
| to | Optional. A comma-separate list of actors
to address. Specified as IRIs. |
| cc | Optional. A comma-separate list of actors
to CC. Specified as IRIs. |
| public | May be true
or false
. |
By default, cc
incldues the publishing actor
's followers collection.
Example:
outbox="$KTISTEC_HOST/actors/$USERNAME/outbox"
activity="{\"type\":\"Publish\",\"content\":\"this is a test\",\"public\":true}"
curl -s -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" -X POST -d "$activity" "$outbox"
Note: "publishing" an object
encompasses both creating a new
object
(an ActivityPub Create
activity) and updating an existing
object
(an ActivityPub Update
activity).
Sharing an Object (ActivityPub Announce
)
To share an object
, POST
a JSON activity
to the outbox
endpoint. The JSON activity
may include the following fields:
| Name | Notes |
| type | Must be "Announce". |
| object | The IRI of the object
being shared. |
| to | Optional. A comma-separate list of actors
to address. Specified as IRIs. |
| cc | Optional. A comma-separate list of actors
to CC. Specified as IRIs. |
| public | May be true
or false
. |
By default, to
includes the actor to which the object
is
attributed, and cc
includes the publishing actor
's followers
collection.
Example:
outbox="$KTISTEC_HOST/actors/$USERNAME/outbox"
activity="{\"type\":\"Announce\",\"object\":\"https://example.com/objects/123\",\"public\":true}"
curl -s -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" -X POST -d "$activity" "$outbox"
Liking an Object
To like an object
, POST
a JSON activity
to the outbox
endpoint. The JSON activity
may include the following fields:
| Name | Notes |
| type | Must be "Like". |
| object | The IRI of the object
being liked. |
| to | Optional. A comma-separate list of actors
to address. Specified as IRIs. |
| cc | Optional. A comma-separate list of actors
to CC. Specified as IRIs. |
| public | May be true
or false
. |
By default, to
includes the actor to which the object
is
attributed, and cc
includes the publishing actor
's followers
collection.
Example:
outbox="$KTISTEC_HOST/actors/$USERNAME/outbox"
activity="{\"type\":\"Like\",\"object\":\"https://example.com/objects/123\",\"public\":true}"
curl -s -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" -X POST -d "$activity" "$outbox"
Following an Actor
To follow an actor, POST
a JSON activity
to the outbox
endpoint. The JSON activity
may include the following fields:
| Name | Notes |
| type | Must be "Follow". |
| object | The IRI of the actor
being followed. |
Example:
outbox="$KTISTEC_HOST/actors/$USERNAME/outbox"
activity="{\"type\":\"Follow\",\"object\":\"https://example.com/actors/alice\"}"
curl -s -H "Authorization Bearer $API_KEY" -H "Content-Type: application/json" -X POST -d "$activity" "$outbox"
Undoing an Activity
You can undo a previously published activity
by POST
ing an Undo
activity
to the outbox
endpoint. The JSON activity
may include
the following fields:
| Name | Notes |
| type | Must be "Undo". |
| object | The IRI of the activity
being undone. |
Example:
outbox="$KTISTEC_HOST/actors/$USERNAME/outbox"
activity="{\"type\":\"Undo\",\"object\":\"https://example.com/activities/123\"}"
curl -s -H "Authorization Bearer $API_KEY" -H "Content-Type: application/json" -X POST -d "$activity" "$outbox"
Deleting
You can delete an actor
or object
by POST
ing a Delete
activity
to the outbox
endpoint. The JSON activity
may include
the following fields:
| Name | Notes |
| type | Must be "Delete". |
| object | The IRI of the actor
or object
being deleted. |
Example:
outbox="$KTISTEC_HOST/actors/$USERNAME/outbox"
activity="{\"type\":\"Delete\",\"object\":\"https://example.com/123\"}"
curl -s -H "Authorization Bearer $API_KEY" -H "Content-Type: application/json" -X POST -d "$activity" "$outbox"
Prerequisites
To run an instance of Ktistec as part of the Fediverse, you'll need a server with a fixed hostname. In the Fediverse, users are identified by (and content is addressed to) a username and a hostname.
Building
You must compile the Ktistec server executable from its source code. You will need to install a recent release of the Crystal programming language. Ktistec requires at least SQLite3 version 3.35.0 (but see notes on Sqlite3 compatibility).
To obtain the source code, clone the Ktistec Github repo.
If you intend to do development on the server, check out the main branch. In addition to Crystal, you'll also need Node.js and Webpack to build the JS and CSS assets from source.
If you just want to build and run the server, check out the dist branch.
To compile the server:
$ crystal build src/ktistec/server.cr
If you're developing on the main branch, build the assets next (skip this step if you're on the dist branch--the latest JS and CSS assets are already built for you):
$ npm run build
Run the compiled executable:
$ LOG_LEVEL=INFO ./server
The first time the server runs it will run the database migrations necessary to create the database. This should only take a few seconds, maximum. When the server is ready to accept connections you will see something like:
Ktistec is ready to lead at http://0.0.0.0:3000
You can now connect to and configure the server.
SQLite3 Compatibility
The following SQLite3 versions are known to have bugs that cause problems for Ktistec:
| SQLite Version | Issue | |--|--| | 3.39.x | problems with bloom filters and recursive queries link | | 3.40.x | problems with bloom filters and recursive queries link |
Running Tests
If you change the code, you should run the tests:
$ crystal spec
Usage
The server runs on port 3000. If you're planning on running it in production, you should put Nginx or Apache in front of it. Don't forget an SSL certificate!
When you run Ktistec for the first time, you'll need to name the server and create the primary user.
Ktistec needs to know the host name of its home on the internet. The server host name is a part of every users's identity in the Fediverse. My identity is "toddsundsted@epiktistes.com". The host name is "epiktistes.com" and other federated servers, and users, know to send posts and other content to me there.
Give the server a site name, too.
After you name the server, you create the primary user. Ktistec currently supports only one user. This is intentional -- one of Ktistec's design goals is to promote a more fully distributed Fediverse.
At a minimum, you need to specify the user's username, password and timezone. You can use a single character for the username if you want, but you'll need six characters, including letters, numbers and symbols, for the password. timezone is any valid IANA time zone database string (e.g. "America/New_York").
Display name and summary are optional.
Once these steps are done, you're running!
Contributors
- Todd Sundsted - creator and maintainer
Copyright and License
Ktistec ActivityPub Server Copyright (C) 2021, 2022, 2023, 2024 Todd Sundsted
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program. If not, see (https://www.gnu.org/licenses/).