Funkwhale Federation
This documentation section is more technical, and targets people who want to understand how Funkwhale’s federation works.
Technologies and standards
Funkwhale’s federation is built on top of the following technologies:
ActivityPub as the high-level federation protocol
HTTP Signatures as the primary mean to authenticate messages
Webfinger to easily retrieve resources using human-friendly names
ActivityStreams and ActivityStreams vocabulary as the mean to structure messages
Support for the following is planned but not implemented-yet:
JSON-LD signatures as an alternate mean to authenticate messages
Philosophy
Our goal is to stick to the specifications as much as possible, to ensure compatibility with existing applications such as Mastodon, Peertube, Plume, Pleroma or PixelFed.
However, this is not always possible for all our use cases. The ActivityPub and ActivityStreams specifications are really high-level and do not always fit our use cases. For such cases, we will use an ad-hoc solution, and document it here.
There are plenty of projects built using ActivityPub, and our goal is not to support all the existing activities. Instead, we want to support activities and objects that make sense for Funkwhale use cases, such as follows or likes.
If you think this document is not accurate or find evidence that Funkwhale is not behaving according to the behaviour documented here, please file a bug on our issue tracker, as we consider this a bug.
Internal logic
This section is relevant if you’re interested in how we handle things internally in our application code.
Database schema
As much as possible, we try to map our internal model and database schema to ActivityPub entities, as this makes things easier to deal with.
We store received activities payload directly in the database before we attempt to process or deliver them. Storing the activities unlock some interesting use cases, such as debugging federation issues, replaying deliveries, or reprocess historical activities that were not supported before.
Each local user is bound to an Actor
. Remote and local actors share the same
database table and all federated entities (such as uploads) are linked to an Actor
and not to a user. This means that, internally, in general, there is no distinction between
local and remote users.
Links:
Activity creation and delivery
When a local actor is making an action that should trigger an Activity
, which roughly is equivalent
to posting an activity to an outbox, we create an object, with the proper payload and store it in our
Activity
table. We then trigger two kind of deliveries:
A delivery to local recipients: for each local recipient, we create an
InboxItem
, linked to the activity. A local actor’s feed is then made of all the available inbox items, which can also have a read/unread statusA delivery to remote recipients: we collect all inboxes and shared inbox urls from remote recipients, and create a
Delivery
object in our database, linked to the initial activity and the inbox or shared inbox url. ThisDelivery
object is then used by our worker to post the activity content to the url.
Receiving an activity from a remote actor in a local inbox is basically the same, but we skip step 2.
Funkwhale does not support all activities, and we have a basic routing logic to handle specific activities, and discard unsupported ones.
If a delivered activity matches one of our routes, a dedicated handler is called,
which can trigger additional logic. For instance, if we receive a Create activity
for an Audio object, our handler will persist the proper data in our local Upload
table, retrieve the audio cover, etc.
Links:
Service actor
In some situations, we will send messages or authenticate our fetches using what we call the service actor. A service actor is an ActivityPub actor object that acts on behalf of a Funkwhale server.
The actor id usually looks like https://yourdomain.com/federation/actors/service
, but
the reliable way to determine it is to query the nodeinfo endpoint and use the value
available in the metadata > actorId
field.
Funkwhale generally considers that the service actor has authority to send activities associated with any object on the same domain. For instance, the service actor could send a Delete activity linked to another users’ library on the same domain.
Supported activities
Follow
Supported on
Library objects
Example of library follow
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{}
],
"type": "Follow",
"id": "https://music.rocks/federation/actors/Alice#follows/99fc40d7-9bc8-4c4a-add1-f637339e1ded",
"actor": "https://music.rocks/federation/actors/Alice",
"to": ["https://awesome.music/federation/actors/Bob"],
"object": "https://awesome.music/federation/music/libraries/dc702491-f6ce-441b-9da0-cecbed08bcc6"
}
In this example, Alice is following the Library described in object
, which is
owned by Bob.
Internal logic
When a follow is received on a Library, Funkwhale will behave differently depending on the visibility of the library:
Automatic accept, when the library is public: a notification is sent to the library owner, and an Accept is sent automatically to the follow actor.
Manual accept, in all other cases: a notification is sent to the library owner. After manual approval from the owner, an Accept is sent to the follow actor.
Funkwhale uses library follow status to grant access to the follow actor. If a library is not public and an actor does not have an approved follow, library content will be inaccessible to the actor.
Checks
Before handling the activity, Funkwhale will ensure the library’s owner is the activity recipient.
Accept
Supported on
Follow objects
Example
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{}
],
"type": "Accept",
"id": "https://music.rocks/federation/actors/Alice#follows/99fc40d7-9bc8-4c4a-add1-f637339e1ded/accept",
"to": ["https://music.rocks/federation/actors/Alice"],
"actor": "https://awesome.music/federation/actors/Bob",
"object": {
"id": "https://music.rocks/federation/actors/Alice#follows/99fc40d7-9bc8-4c4a-add1-f637339e1ded",
"type": "Follow",
"actor": "https://music.rocks/federation/actors/Alice",
"object": "https://awesome.music/federation/music/libraries/dc702491-f6ce-441b-9da0-cecbed08bcc6",
},
}
In this example, Bob accepts Alice’s follow.
Internal logic
When an Accept is received with a Follow object, the corresponding follow is marked as accepted in the database.
For library follows, this means that the actor will receive future activities occurring within this library, such as Create Audio, Delete Audio or Delete Library
The follow actor will also be able to browse the library pages and download the library’s audio files. Have a look at Audio fetching on restricted libraries for more details.
Checks
Before handling the activity, Funkwhale will ensure the accept comes from the library’s owner.
Undo
Supported on
Follow objects
Example
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{}
],
"type": "Undo",
"id": "https://music.rocks/federation/actors/Alice#follows/99fc40d7-9bc8-4c4a-add1-f637339e1ded/accept",
"to": ["https://awesome.music/federation/actors/Bob"],
"actor": "https://music.rocks/federation/actors/Alice",
"object": {
"id": "https://music.rocks/federation/actors/Alice#follows/99fc40d7-9bc8-4c4a-add1-f637339e1ded",
"type": "Follow",
"actor": "https://music.rocks/federation/actors/Alice",
"object": "https://awesome.music/federation/music/libraries/dc702491-f6ce-441b-9da0-cecbed08bcc6",
},
}
In this example, Alice is notifying Bob she’s undoing her follow.
Internal logic
When an undo is received, the corresponding follow is deleted from the database.
Checks
Before handling the activity, Funkwhale will ensure the undo actor is the follow actor.
Create
Supported on
Audio objects
Example
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{}
],
"to": [
"https://awesome.music/federation/music/libraries/dc702491-f6ce-441b-9da0-cecbed08bcc6/followers"
],
"type": "Create",
"actor": "https://awesome.music/federation/actors/Bob",
"object": {}
}
Note
Refer to Audio to see the structure of the object
attribute.
Internal logic
When a Create is received with an Audio object, Funkwhale will persist
a local upload and bind it to the proper library and track. If no local track
match the audio metadata, a track is created using the metadata
attribute
from the Audio object.
Checks
Before handling the activity, Funkwhale will ensure the activity actor and the audio library’s actor are the same.
If no local actor follows the audio’s library, the activity will be discarded.
Update
Supported on
Example
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{}
],
"to": [
"https://awesome.music/federation/music/libraries/dc702491-f6ce-441b-9da0-cecbed08bcc6/followers"
],
"type": "Update",
"actor": "https://awesome.music/federation/actors/Bob",
"object": {}
}
Internal logic
When a Update is received with a Library or Track object, Funkwhale will try to update the local copy of the corresponding object in it’s database.
Checks
Checks vary depending of the type of object associated with the update.
For Library objects, we ensure the actor sending the message is the owner of the library.
For musical entities such as Track, we ensure the actor sending the message matches the attributedTo property declared on the local copy on the object, or the Service actor.
Delete
Supported on
Example (on Library)
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{}
],
"type": "Delete",
"to": [
"https://awesome.music/federation/music/libraries/dc702491-f6ce-441b-9da0-cecbed08bcc6/followers"
],
"actor": "https://awesome.music/federation/actors/Bob",
"object": {
"type": "Library",
"id": "https://awesome.music/federation/music/libraries/dc702491-f6ce-441b-9da0-cecbed08bcc6"
}
}
Example (on Audio)
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{}
],
"type": "Delete",
"to": [
"https://awesome.music/federation/music/libraries/dc702491-f6ce-441b-9da0-cecbed08bcc6/followers"
],
"actor": "https://awesome.music/federation/actors/Bob",
"object": {
"type": "Audio",
"id": [
"https://awesome.music/federation/music/uploads/19420073-3572-48a9-8c6c-b385ee1b7905",
"https://awesome.music/federation/music/uploads/11d99680-23c6-4f72-997a-073b980ab204",
"https://awesome.music/federation/music/uploads/1efadc1c-a704-4b8a-a71a-b288b1d1f423"
]
}
}
In this example, Bob notifies the followers of their library that 3 objects were deleted.
Note
For performance reason, when deleting Audio objects, Funkwhale supports either a list of ids or a single id.
Internal logic
When a Delete is received, the corresponding objects are immediately deleted from the database.
Checks
Before handling deletion, Funkwhale ensures the actor initiating the activity is the owner of the deleted Audio or Library.
Supported objects
Artist
Note
This object is not standard.
Example
{
"type": "Artist",
"id": "https://awesome.music/federation/music/artists/73c32807-a199-4682-8068-e967f734a320",
"name": "Metallica",
"published": "2018-04-08T12:19:05.920415+00:00",
"musicbrainzId": "65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab"
}
Structure
id (required): a uri identifying the artist over federation
name (required): a name for the artist
published (required): the publication date of the artist (on the federation)
musicbrainzId (optional): the musicbrainz artist id
Album
Note
This object is not standard.
Example
{
"type": "Album",
"id": "https://awesome.music/federation/music/albums/69d488b5-fdf6-4803-b47c-9bb7098ea57e",
"name": "Ride the Lightning",
"released": "1984-01-01",
"published": "2018-10-02T19:49:17.412546+00:00",
"musicbrainzId": "589ff96d-0be8-3f82-bdd2-299592e51b40",
"cover": {
"href": "https://awesome.music/media/albums/covers/2018/10/02/b69d398b5-fdf6-4803-b47c-9bb7098ea57e.jpg",
"type": "Link",
"mediaType": "image/jpeg"
},
"artists": [
{}
]
}
Structure
id (required): a uri identifying the album over federation
name (required): the title of the album
artists (required): a list of Artist objects involved in the album
published (required): the publication date of the entity (on the federation)
released (optional): the release date of the album
musicbrainzId (optional): the musicbrainz release id
cover (optional): a Link object representing the album cover
Track
Note
This object is not standard.
Example
{
"type": "Track",
"id": "https://awesome.music/federation/music/tracks/82ece296-6397-4e26-be90-bac5f9990240",
"name": "For Whom the Bell Tolls",
"position": 3,
"published": "2018-10-02T19:49:35.822537+00:00",
"musicbrainzId": "771ab043-8821-44f9-b8e0-2733c3126c6d",
"artists": [
{}
],
"album": {}
}
Structure
id (required): a uri identifying the track over federation
name (required): the title of the track
position (required): the position of the Track in the album
published (required): the publication date of the entity (on the federation)
musicbrainzId (optional): the musicbrainz recording id
album (required): the Album that contains the track
artists (required): a list of Artist objects involved in the track (they can differ from the album artists)
Library
Note
This object is not standard but inherits its behaviour and properties from Actor and Collection.
Example
{
"type": "Library",
"id": "https://awesome.music/federation/music/libraries/dc702491-f6ce-441b-9da0-cecbed08bcc6",
"attributedTo": "https://awesome.music/federation/actors/Alice",
"name": "My awesome library",
"followers": "https://awesome.music/federation/music/libraries/dc702491-f6ce-441b-9da0-cecbed08bcc6/followers",
"summary": "This library is for restricted use only",
"totalItems": 4234,
"first": "https://awesome.music/federation/music/libraries/dc702491-f6ce-441b-9da0-cecbed08bcc6?page=1",
"last": "https://awesome.music/federation/music/libraries/dc702491-f6ce-441b-9da0-cecbed08bcc6?page=56",
}
Structure
id (required): a uri identifying the library over federation
actor (required): the id of the actor managing the library
name (required): the name of the library
followers (required): the id of the library’s followers collection
totalItems (required): the number of audio objects available in the library
first (required): the URL of the first page of the library
last (required): the URL of the last page of the library
summary (optional): a description for the library
Note
Crawling library pages requires authentication and an approved follow, unless the library is public.
Audio
Note
This object is specified in ActivityStreams, but Funkwhale needs non-standard attributes to handle it.
Example
{
"type": "Audio",
"id": "https://awesome.music/federation/music/uploads/88f0bc20-d7fd-461d-a641-dd9ac485e096",
"name": "For Whom the Bell Tolls - Ride the Lightning - Metallica",
"size": 8656581,
"bitrate": 320000,
"duration": 213,
"library": "https://awesome.music/federation/music/libraries/dc702491-f6ce-441b-9da0-cecbed08bcc6",
"updated": "2018-10-02T19:49:35.646372+00:00",
"published": "2018-10-02T19:49:35.646359+00:00",
"track": {},
"url": {
"href": "https://awesome.music/api/v1/listen/82ece296-6397-4e26-be90-bac5f9990240/?upload=88f0bc20-d7fd-461d-a641-dd9ac485e096",
"type": "Link",
"mediaType": "audio/mpeg"
}
}
Structure
id (required): a uri identifying the audio over federation
name (required): a human-friendly title for the audio (We concatenate track name, album title and artist name)
size (required, non-standard): the size of the audio, in bytes
bitrate (required, non-standard): the bitrate of the audio, in bytes/s
duration (required): the duration of the audio, in seconds
library (required, non-standard): the id of the Library object that contains the object
published (required): the publication date of the entity (on the federation)
updated (required): the last update date of the entity (on the federation)
url (required): a
Link
object with anaudio/
mediaType where the audio file is downloadabletrack (required, non-standard): the Track the Audio is bound to
Note
Accessing the Audio file via its url requires authentication and an approved follow on the containing library, unless the library is public.
Audio fetching on restricted libraries
Library and Audio url objects may require additional authentication to be accessed.
For Library objects:
If the library is public, library pages can be accessed without restriction
Otherwise, the HTTP request must be signed by an actor with an approved follow on the library
For Audio url objects:
If the audio’s library is public, audio file can be accessed without restriction
Otherwise, the HTTP request must be signed by an actor with an approved follow on the audio’s library
Properties
attributedTo
Funkwhale will generally use the attributedTo
property to communicate
who is responsible for a given object. When an object has the attributedTo
attribute,
the associated actor has the permission to Update, Delete or
more generally apply any kind of activity on the object.
In addition, Funkwhale consider all the objects of a domain as attributed to its corresponding Service actor.