Skip to content

Commit 8513a46

Browse files
committed
Manage ActivityPub interface to broadcast videos and get external ones
1 parent 2e9bcf0 commit 8513a46

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+5948
-188
lines changed

.env.dev-exemple

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ REDIS_TAG=redis:alpine3.16
1010
### In case of value changing, you have to rebuild and restart your container.
1111
### All yours datas will be kept.
1212
DOCKER_ENV=light
13+
## PEERTUBE SECRETS
14+
POSTGRES_PASSWORD=<PWD>
15+
PEERTUBE_SECRET=<SECRET>

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ pod/main/static/custom/img
5050
!pod/custom/settings_local.py.example
5151
settings_local.py
5252
transcription/*
53+
docker-volume
5354

5455
# Unit test utilities #
5556
#######################
@@ -73,6 +74,7 @@ compile-model
7374
*.crt
7475
*.key
7576
*.pem
77+
*.pub
7678

7779
# NPM stuffs #
7880
################

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,9 @@ endif
174174
sudo rm -rf ./pod/db.sqlite3
175175
sudo rm -rf ./pod/db_remote.sqlite3
176176
sudo rm -rf ./pod/media
177+
178+
# Ouvre un shell avec le contexte Django dans le conteneur pod
179+
shell:
180+
docker exec -it pod-activitypub-with-volumes pip install ipython
181+
docker exec -it pod-activitypub-with-volumes env DJANGO_SETTINGS_MODULE=pod.settings ipython --ext=autoreload -c "%autoreload 2" -i
182+
# then it is needed to call import django; django.setup()

docker-compose-full-dev-with-volumes.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,18 @@ services:
5656
- ./.env.dev
5757
volumes: *pod-volumes
5858

59+
pod-activitypub:
60+
container_name: pod-activitypub-with-volumes
61+
build:
62+
context: .
63+
dockerfile: dockerfile-dev-with-volumes/pod-activitypub/Dockerfile
64+
depends_on:
65+
- pod-back
66+
- redis
67+
env_file:
68+
- ./.env.dev
69+
volumes: *pod-volumes
70+
5971
elasticsearch:
6072
container_name: elasticsearch-with-volumes
6173
hostname: elasticsearch.localhost
@@ -79,6 +91,37 @@ services:
7991
ports:
8092
- 6379:6379
8193

94+
peertube:
95+
container_name: peertube
96+
hostname: peertube.localhost
97+
image: chocobozzz/peertube:develop-bookworm
98+
ports:
99+
- 9000:9000
100+
- 3000:3000
101+
env_file:
102+
- ./dockerfile-dev-with-volumes/peertube/peertube.env
103+
- ./.env.dev
104+
depends_on:
105+
- postgres
106+
- redis
107+
- postfix
108+
command: sh -c "yarn install && npm run dev"
109+
restart: "always"
110+
111+
postgres:
112+
image: postgres:13-alpine
113+
env_file:
114+
- ./dockerfile-dev-with-volumes/peertube/peertube.env
115+
- ./.env.dev
116+
restart: "always"
117+
118+
postfix:
119+
image: mwader/postfix-relay
120+
env_file:
121+
- ./dockerfile-dev-with-volumes/peertube/peertube.env
122+
- ./.env.dev
123+
restart: "always"
124+
82125
# redis-commander:
83126
# container_name: redis-commander
84127
# hostname: redis-commander.localhost
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
NODE_VERSION=21.7.1
2+
NODE_ENV=dev
3+
NODE_DB_LOG=false
4+
5+
# Database / Postgres service configuration
6+
POSTGRES_USER=postgres
7+
# Postgres database name "peertube"
8+
POSTGRES_DB=peertube_dev
9+
# The database name used by PeerTube will be PEERTUBE_DB_NAME (only if set) *OR* 'peertube'+PEERTUBE_DB_SUFFIX
10+
#PEERTUBE_DB_NAME=<MY POSTGRES DB NAME>
11+
#PEERTUBE_DB_SUFFIX=_prod
12+
# Database username and password used by PeerTube must match Postgres', so they are copied:
13+
PEERTUBE_DB_USERNAME=$POSTGRES_USER
14+
PEERTUBE_DB_PASSWORD=$POSTGRES_PASSWORD
15+
PEERTUBE_DB_SSL=false
16+
# Default to Postgres service name "postgres" in docker-compose.yml
17+
PEERTUBE_DB_HOSTNAME=postgres
18+
19+
# PeerTube server configuration
20+
# If you test PeerTube in local: use "peertube.localhost" and add this domain to your host file resolving on 127.0.0.1
21+
PEERTUBE_WEBSERVER_HOSTNAME=peertube.localhost
22+
# If you just want to test PeerTube on local
23+
PEERTUBE_WEBSERVER_PORT=9000
24+
PEERTUBE_WEBSERVER_HTTPS=false
25+
# If you need more than one IP as trust_proxy
26+
# pass them as a comma separated array:
27+
PEERTUBE_TRUST_PROXY=["127.0.0.1", "loopback", "172.18.0.0/16"]
28+
29+
# E-mail configuration
30+
# If you use a Custom SMTP server
31+
#PEERTUBE_SMTP_USERNAME=
32+
#PEERTUBE_SMTP_PASSWORD=
33+
# Default to Postfix service name "postfix" in docker-compose.yml
34+
# May be the hostname of your Custom SMTP server
35+
PEERTUBE_SMTP_HOSTNAME=postfix
36+
PEERTUBE_SMTP_PORT=25
37+
PEERTUBE_SMTP_FROM=[email protected]
38+
PEERTUBE_SMTP_TLS=false
39+
PEERTUBE_SMTP_DISABLE_STARTTLS=false
40+
PEERTUBE_ADMIN_EMAIL=[email protected]
41+
42+
# Postfix service configuration
43+
POSTFIX_myhostname=example.org
44+
# If you need to generate a list of sub/DOMAIN keys
45+
# pass them as a whitespace separated string <DOMAIN>=<selector>
46+
OPENDKIM_DOMAINS=example.org=peertube
47+
# see https://github.com/wader/postfix-relay/pull/18
48+
OPENDKIM_RequireSafeKeys=no
49+
50+
PEERTUBE_OBJECT_STORAGE_UPLOAD_ACL_PUBLIC="public-read"
51+
PEERTUBE_OBJECT_STORAGE_UPLOAD_ACL_PRIVATE="private"
52+
53+
#PEERTUBE_LOG_LEVEL=info
54+
55+
# /!\ Prefer to use the PeerTube admin interface to set the following configurations /!\
56+
#PEERTUBE_SIGNUP_ENABLED=true
57+
#PEERTUBE_TRANSCODING_ENABLED=true
58+
#PEERTUBE_CONTACT_FORM_ENABLED=true
59+
60+
PEERTUBE_REDIS_HOSTNAME=redis-with-volumes
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#------------------------------------------------------------------------------------------------------------------------------
2+
# (\___/)
3+
# (='.'=) Dockerfile multi-stages node & python
4+
# (")_(")
5+
#------------------------------------------------------------------------------------------------------------------------------
6+
# Conteneur node
7+
ARG PYTHON_VERSION
8+
# TODO
9+
#FROM harbor.urba.univ-lille.fr/store/node:19 as source-build-js
10+
11+
#------------------------------------------------------------------------------------------------------------------------------
12+
# Conteneur python
13+
FROM $PYTHON_VERSION
14+
WORKDIR /tmp/pod
15+
COPY ./pod/ .
16+
# TODO
17+
#FROM harbor.urba.univ-lille.fr/store/python:3.7-buster
18+
19+
RUN apt-get clean && apt-get update && apt-get install -y netcat
20+
21+
WORKDIR /usr/src/app
22+
23+
COPY ./requirements.txt .
24+
COPY ./requirements-conteneur.txt .
25+
COPY ./requirements-encode.txt .
26+
COPY ./requirements-dev.txt .
27+
28+
RUN pip3 install --no-cache-dir -r requirements-dev.txt \
29+
&& pip3 install elasticsearch==7.17.9
30+
31+
# ENTRYPOINT :
32+
COPY ./dockerfile-dev-with-volumes/pod-activitypub/my-entrypoint-activitypub.sh /tmp/my-entrypoint-activitypub.sh
33+
RUN chmod 755 /tmp/my-entrypoint-activitypub.sh
34+
35+
ENTRYPOINT ["bash", "/tmp/my-entrypoint-activitypub.sh"]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/sh
2+
echo "Launching commands into pod-dev"
3+
until nc -z pod.localhost 8000; do echo waiting for pod-back; sleep 10; done;
4+
# Worker ActivityPub
5+
celery --app pod.activitypub.tasks worker --loglevel INFO --queues activitypub --concurrency 1 --hostname activitypub
6+
sleep infinity

pod/activitypub/README.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# ActivityPub implementation
2+
3+
Pod implements a minimal set of ActivityPub that allows video sharing between Pod instances.
4+
The ActivityPub implementation is also compatible with Peertube.
5+
6+
## Federation
7+
8+
Here is what happens when two instances, say *Node A* and *Node B* (being Pod or Peertube) federate with each other, in a one way federation.
9+
10+
### Federation
11+
12+
- An administrator asks for Node A to federate with Node B
13+
- Node A reaches the [NodeInfo](https://github.com/jhass/nodeinfo/blob/main/PROTOCOL.md) endpoint (`/.well-known/nodeinfo`) on Node B and discover the root application endpoint URL.
14+
- Node A reaches the root application endpoint (for Pod this is `/ap/`) and get the `inbox` URL.
15+
- Node A sends a `Create` activity for a `Follow` object on the Node B root application `inbox`.
16+
- Node B reads the Node A root application endpoint URL in the `Follow` objects, reaches this endpoint and get the Node A root application `inbox` URL.
17+
- Node B creates a `Follower` objects and stores it locally
18+
- Node B sends a `Accept` activity for the `Follower` object on Node A root application enpdoint.
19+
- Later, Node A can send to Node B a `Undo` activity for the `Follow` object to de-federate.
20+
21+
### Video discovery
22+
23+
- Node A reaches the Node B root application `outbox`.
24+
- Node A browse the pages of the `outbox` and look for announces about `Videos`
25+
- Node A reaches the `Video` endpoints and store locally the information about the videos.
26+
27+
### Video creation and update sharing
28+
29+
#### Creation
30+
31+
- A user of Node B publishes a `Video`
32+
- Node B sends a `Announce` activity on the `inbox` of all its `Followers`, including Node A with the ID of the new video.
33+
- Node A reads the information about the new `Video` on Node B video endpoint.
34+
35+
#### Edition
36+
37+
- A user of Node B edits a `Video`
38+
- Node B sends a `Update` activity on the `inbox` of all its `Followers`, including Node A with the ID of the new video, containing the details of the `Video`.
39+
40+
#### Deletion
41+
42+
- A user of Node B deletes a `Video`
43+
- Node B sends a `Delete` activity on the `inbox` of all its `Followers`, including Node A with the ID of the new video.
44+
45+
## Implementation
46+
47+
The ActivityPub implementation tries to replicate the network messages of Peertube.
48+
There may be things that could have been done differently while still following the ActivityPub specs, but changing the network exchanges would require checking if the Peertube compatibility is not broken.
49+
This is due to Peertube having a few undocumented behaviors that are not exactly part of the AP specs.
50+
51+
To achieve compatibility with Peertube, Pod implements two specifications to sign ActivityPub exchanges.
52+
53+
- [Signing HTTP Messages, draft 12](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12).
54+
This specification is replaced by [RFC9421](https://www.rfc-editor.org/rfc/rfc9421.html) but Peertube does not implement the finale spec,
55+
and instead lurks on the writing of the [ActivityPub and HTTP Signatures](https://swicg.github.io/activitypub-http-signature/) spec, that is also still a draft.
56+
See the [related discussion](https://framacolibri.org/t/rfc9421-replaces-the-signing-http-messages-draft/20911/2).
57+
This spec describe how to sign ActivityPub payload with HTTP headers.
58+
- [Linked Data Signatures 1.0](https://web.archive.org/web/20170717200644/https://w3c-dvcg.github.io/ld-signatures/) draft.
59+
This specification is replaced by [Verifiable Credential Data Integrity](https://w3c.github.io/vc-data-integrity/) but Peertube does not implement the finale spec.
60+
This spec describe how to sign ActivityPub payload by adding fields in the payload.
61+
62+
The state of the specification support in Peertube is similar to [Mastodon](https://docs.joinmastodon.org/spec/security/), and is probably a mean to keep the two software compatible with each other.
63+
64+
## Limitations
65+
66+
- Peertube instance will only be able to index Pod videos if the video thumbnails are absent.
67+
- Peertube instance will only be able to index Pod videos if the thumbnails are in JPEG format.
68+
png thumbnails are not supported at the moment (but that may come in the future
69+
[more details here](https://framacolibri.org/t/comments-and-suggestions-on-the-peertube-activitypub-implementation/21215)).
70+
In the meantime, pod fakes the mime-type of all thumbnails to be JPEG, even when they actually are PNGs.
71+
72+
## Configuration
73+
74+
A RSA keypair is needed for ActivityPub to work, and passed as
75+
`ACTIVITYPUB_PUBLIC_KEY` and `ACTIVITYPUB_PRIVATE_KEY` configuration settings.
76+
They can be generated from a python console:
77+
78+
```python
79+
from Crypto.PublicKey import RSA
80+
81+
activitypub_key = RSA.generate(2048)
82+
83+
# Generate the private key
84+
# Add the content of this command in 'pod/custom/settings_local.py'
85+
# in a variable named ACTIVITYPUB_PRIVATE_KEY
86+
with open("pod/activitypub/ap.key", "w") as fd:
87+
fd.write(activitypub_key.export_key().decode())
88+
89+
# Generate the public key
90+
# Add the content of this command in 'pod/custom/settings_local.py'
91+
# in a variable named ACTIVITYPUB_PUBLIC_KEY
92+
with open("pod/activitypub/ap.pub", "w") as fd:
93+
fd.write(activitypub_key.publickey().export_key().decode())
94+
```
95+
96+
The federation also needs celery to be configured with `ACTIVITYPUB_CELERY_BROKER_URL`.
97+
98+
Here is a sample working activitypub `pod/custom/settings_local.py`:
99+
100+
```python
101+
ACTIVITYPUB_CELERY_BROKER_URL = "redis://redis:6379/5"
102+
103+
with open("pod/activitypub/ap.key") as fd:
104+
ACTIVITYPUB_PRIVATE_KEY = fd.read()
105+
106+
with open("pod/activitypub/ap.pub") as fd:
107+
ACTIVITYPUB_PUBLIC_KEY = fd.read()
108+
```
109+
110+
## Development
111+
112+
The `DOCKER_ENV` environment var should be set to `full` so a peertube instance and a ActivityPub celery worker are launched.
113+
Then peertube is available at http://peertube.localhost:9000.
114+
115+
### Federate Peertube with Pod
116+
117+
- Sign in with the `root` account
118+
- Go to [Main menu > Administration > Federation](http://peertube.localhost:9000/admin/follows/following-list) > Follow
119+
- Open the *Follow* modal and type `pod.localhost:8000`
120+
121+
### Federate Pod with Peertube
122+
123+
- Sign in with `admin`
124+
- Go to the [Administration pannel > Followings](http://pod.localhost:8000/admin/activitypub/following/) > Add following
125+
- Type `http://peertube.localhost:9000` in *Object* and save
126+
- On the [Followings list](http://pod.localhost:8000/admin/activitypub/following/) select the new object, and select `Send the federation request` in the action list, refresh.
127+
- If the status is *Following request accepted* then select the object again, and choose `Reindex instance videos` in the action list.
128+
129+
## Shortcuts
130+
131+
### Manual AP request
132+
133+
```shell
134+
curl -H "Accept: application/activity+json, application/ld+json" -s "http://pod.localhost:8000/accounts/peertube" | jq
135+
```
136+
137+
### Unit tests
138+
139+
```shell
140+
python manage.py test --settings=pod.main.test_settings pod.activitypub.tests
141+
```

pod/activitypub/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)