diff --git a/changes/changelog.d/549.documentation b/changes/changelog.d/549.documentation new file mode 100644 index 000000000..a98dc80b2 --- /dev/null +++ b/changes/changelog.d/549.documentation @@ -0,0 +1 @@ +Technical documentation of music federation (#549) diff --git a/docs/federation.rst b/docs/federation.rst deleted file mode 100644 index 08658b12e..000000000 --- a/docs/federation.rst +++ /dev/null @@ -1,56 +0,0 @@ -Federation -========== - -Each Funkwale instance can federates its music library with other instances -of the network. This means that an instance A can acquire music from instance B -and share its own library with an instance C. - -We support various levels of controls for federation-related features. - -Managing federation -------------------- - -Federation management is only available to instance admins and users -who have the proper permissions. You can disable federation completely -at the instance level by editing the ``federation__enabled`` :ref:`setting `. - -On the front end, assuming you have the proper permission, you will see -a "Federation" link in the sidebar. - - -Acquire music via federation ----------------------------- - -Instance libraries are protected by default. To access another instance -library, you have to follow it. Each Funkwhale instance gets a dedicated -ActivityPub Actor you can follow via the username "library@yourinstance.domain". - -When submitted, a follow request will be sent to -the other instance which can accept or deny it. Once your follow request -is accepted, you can start browsing the other instance library -and import music from it. - -By default, we do not duplicate audio files from federated tracks, to reduce -disk usage on your instance. When someone listens to a federated track, -the audio file is requested on the fly from the remote instance, and -store in a local cache. It is automatically deleted after a configurable -amount of time if it was not listened again in the meantime. - -If you want to mirror a remote instance collection, including its audio files, -we offer an option for that. - -We also support an "autoimport" mode for each remote library. When enabled, -any new track published in the remote library will be directly imported -in your instance. - -Share music via federation --------------------------- - -Federation is enabled by default, but requires manually approving -each other instance asking for access to library. This is by design, -to ensure your library is not shared publicly without your consent. - -However, if you're confident about federating publicly without manual approval, -you can set the ``federation__music_needs_approval`` :ref:`setting ` to false. -Follow requests will be accepted automatically and followers -given access to your library without manual intervention. diff --git a/docs/federation/index.rst b/docs/federation/index.rst new file mode 100644 index 000000000..cbc8cd155 --- /dev/null +++ b/docs/federation/index.rst @@ -0,0 +1,615 @@ +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 retrive 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 authentify messages + +.. _ActivityPub: https://www.w3.org/TR/activitypub/ +.. _HTTP Signatures: https://tools.ietf.org/id/draft-cavage-http-signatures-01.html +.. _Webfinger: https://tools.ietf.org/html/rfc7033 +.. _JSON-LD signatures: https://w3c-dvcg.github.io/ld-signatures/ +.. _ActivityStreams: https://www.w3.org/TR/activitystreams-core/ +.. _ActivityStreams vocabulary: https://www.w3.org/TR/activitystreams-vocabulary/ + +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: + +- `Federation models `_ + + +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: + +1. 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 + status +2. A 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. + This ``Delivery`` 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 2#. + +Funkwhale does not support all activities, and we have a basic routing logic to handle +specific activities, and discard unsupported ones. Unsupported activities are still +received and stored though. + +If a delivered activity match one of our routes, a dedicated handler is called, +which can trigger additionnal logic. For instance, if we receive a :ref:`activity-create` activity +for an :ref:`object-audio` object, our handler will persist the proper data in our local ``Upload`` +table, retrieve the audio cover, etc. + +Links: + +- `Routing logic for activities `_ +- `Delivery logic for activities `_ + + +Supported activities +-------------------- + +.. _activity-follow: + +Follow +^^^^^^ + +Supported on +************ + +- :ref:`object-Library` objects + +Example of library follow +************************* + +.. code-block:: json + + { + "@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 :ref:`object-library` described in ``object``, which is +owned by Bob. + +Internal logic +************** + +When a follow is received on a :ref:`object-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 :ref:`activity-accept` is sent automatically to the follow actor. +- Manuel accept, in all other cases: a notification is sent to the library owner. After manual approval from the owner, an :ref:`activity-accept` is sent to the follow actor. + +Funkwhale uses library follows 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 must be +inaccessible to the actor. + +Checks +****** + +Before handling the activity, Funkwhale will ensure the library's owner is +the activity recipient. + +.. _activity-accept: + +Accept +^^^^^^ + +Supported on +************ + +- :ref:`activity-follow` objects + +Example +******* + +.. code-block:: json + + { + "@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 :ref:`activity-accept` is received with a :ref:`activity-follow` object, the corresponding follow +is marked as accepted in the database. + +For library follows, this means that the actor will receive future +activities occuring within this library, such as :ref:`activity-create` :ref:`object-audio`, +:ref:`activity-delete` :ref:`object-audio` or :ref:`activity-delete` :ref:`object-library` + +The follow actor will also be able to browse the library pages and download the library's +audio files. Have a look at :ref:`library-access` for more details. + + +Checks +****** + +Before handling the activity, Funkwhale will ensure the accept comes from +the library's owner. + +.. _activity-undo: + +Undo +^^^^ + +Supported on +************ + +- :ref:`activity-follow` objects + +Example +******* + +.. code-block:: json + + { + "@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. + +.. _activity-create: + +Create +^^^^^^ + +Supported on +************ + +- :ref:`object-audio` objects + +Example +******* + +.. code-block:: json + + { + "@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 :ref:`object-audio` to see the structure of the ``object`` attribute. + +Internal logic +************** + +When a :ref:`activity-create` is received with an :ref:`object-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 :ref:`object-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. + +.. _activity-delete: + +Delete +^^^^^^ + +Supported on +************ + +- :ref:`object-audio` objects +- :ref:`object-Library` objects + +Example (on :ref:`object-Library`) +************************ + +.. code-block:: json + + { + "@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 :ref:`object-audio`) +********************** + +.. code-block:: json + + { + "@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 :ref:`object-audio` objects, Funkwhale support + either a list of ids or a single id. + +Internal logic +************** + +When a :ref:`activity-delete` is received, the corresponding objects are deleted immediatly +from the database. + +Checks +****** + +Before handling deletion, Funkwhale ensure the actor initiating the activity +is the owner of the deleted :ref:`object-audio` or :ref:`object-Library`. + +Supported objects +----------------- + +.. _object-artist: + +Artist +^^^^^^ + +.. note:: + + This object is not standard. + +Example +******* + +.. code-block:: json + + { + "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 + +.. _object-album: + +Album +^^^^^ + +.. note:: + + This object is not standard. + +Example +******* + +.. code-block:: json + + { + "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 :ref:`object-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 + +.. _object-track: + +Track +^^^^^ + +.. note:: + + This object is not standard. + +Example +******* + +.. code-block:: json + + { + "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 :ref:`object-track` in the album +- **published** (required): the publication date of the entity (on the federation) +- **musicbrainzId** (optional): the musicbrainz recording id +- **album** (required): the :ref:`object-album` that contains the track +- **artists** (required): a list of :ref:`object-artist` objects involved in the track (they can differ fro mthe album artists) + +.. _object-library: + +Library +^^^^^^^ + +.. note:: + + This object is not standard but inherits its behaviour and properties from + Actor and Collection. + + +Example +******* + +.. code-block:: json + + { + "type": "Library", + "id": "https://awesome.music/federation/music/libraries/dc702491-f6ce-441b-9da0-cecbed08bcc6", + "actor": "https://awesome.music/federation/actors/MyNameIsTroll", + "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. + +.. _object-audio: + +Audio +^^^^^ + +.. note:: + + This object `is specified in ActivityStreams `_, + but Funkwhale needs non-standard attributes to handle it. + +Example +******* + +.. code-block:: json + + { + "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 :ref:`object-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 an ``audio/`` mediaType where the audio file is downloadable +- **track** (required, non-standard): the :ref:`object-track` the :ref:`object-audio` is bound to + +.. note:: + + Accessing the Audio file via its url requires authentication and an approved follow on the upload library, + unless the library is public. + + +.. _library-access: + +Audio fetching on restricted libraries +-------------------------------------- + +:ref:`object-library` and :ref:`object-audio` url objects may require additional authentications +to be accessed. + +For :ref:`object-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 :ref:`object-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 diff --git a/docs/index.rst b/docs/index.rst index b0d89f003..ef02db64b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -19,7 +19,7 @@ Funkwhale is a self-hosted, modern free and open-source music server, heavily in configuration troubleshooting importing-music - federation + federation/index api third-party contributing