Compare commits

...

26 commits

Author SHA1 Message Date
71f4de937b Merge branch 'develop' into fedi-absturztau-be 2022-10-19 17:40:12 +02:00
f36d14818d Unilateral remove from followers (#232)
from https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3647/

Co-authored-by: marcin mikołajczak <git@mkljczk.pl>
Co-authored-by: Tusooa Zhu <tusooa@kazv.moe>
Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/akkoma#232
2022-10-19 10:01:14 +00:00
5231d436d1 Add docker migration guide 2022-10-18 16:16:55 +01:00
deba1d25f5 add DB restart to docker file 2022-10-17 16:29:36 +01:00
66f913355a Docker builds (#231)
Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/akkoma#231
2022-10-16 19:25:54 +00:00
60b3c8d17b bump version 2022-10-14 12:49:35 +01:00
edf7d5089f Merge pull request 'Check that the signature matches the creator' (#230) from domain-blocks into develop
Reviewed-on: AkkomaGang/akkoma#230
2022-10-14 11:41:34 +00:00
d4bdd3ddb7 Merge pull request 'SQL optimisations' (#227) from i-hate-sql into develop
Reviewed-on: AkkomaGang/akkoma#227
2022-10-14 10:49:02 +00:00
03662501c3 Check that the signature matches the creator 2022-10-14 11:48:32 +01:00
856c57208b Ensure deletes are handled after everything else 2022-10-11 14:30:08 +01:00
cb9b0d3720 optimise notifications query 2022-10-11 11:40:43 +01:00
8af50dea36 format 2022-10-10 17:13:42 +01:00
ca9e6ffc55 Use inner lateral join to not get dropped in :total 2022-10-10 16:45:02 +01:00
574f010bc8 Extract deactivated users query to a join 2022-10-10 15:55:58 +01:00
c6e63aaf6b Backend settings sync (#226)
Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/akkoma#226
2022-10-06 16:22:15 +00:00
07295f7c8c Merge pull request 'include requirement to enable HTTP tunnel in tor' (#224) from tor-docs into develop
Reviewed-on: AkkomaGang/akkoma#224
2022-09-20 13:43:14 +00:00
47a793f33e include requirement to enable HTTP tunnel in tor 2022-09-20 14:40:32 +01:00
7775cefd73 Merge pull request 'ensure we use the same OTP for all releases' (#223) from update-otp-version into develop
Reviewed-on: AkkomaGang/akkoma#223
2022-09-20 12:33:16 +00:00
69099d6f44 ensure we use the same OTP for all releases 2022-09-20 12:20:54 +01:00
5827f7781f Add installation note about flavour, support special cases (#222)
Fixes #210

Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/akkoma#222
2022-09-20 11:04:26 +00:00
b2aa82cee5 Fix false error in meilisearch index (#221)
the schema changed

https://docs.meilisearch.com/reference/api/documents.html#add-or-update-documents

this wasn't breaking anything, it would just report errors that were actually successes

Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/akkoma#221
2022-09-20 10:36:21 +00:00
9b2c169cef Merge pull request 'Move remote user interaction changelog entry to correct version' (#219) from norm/akkoma:changelog-remote-user-interaction into develop
Reviewed-on: AkkomaGang/akkoma#219
2022-09-19 17:33:32 +00:00
561e1f2470 Make backups require its own scope (#218)
Pulled from https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3721.

This makes backups require its own scope (`read:backups`) instead of the `read:accounts` scope.

Co-authored-by: Tusooa Zhu <tusooa@kazv.moe>
Reviewed-on: AkkomaGang/akkoma#218
Co-authored-by: Norm <normandy@biribiri.dev>
Co-committed-by: Norm <normandy@biribiri.dev>
2022-09-19 17:31:35 +00:00
0aabe4d0c3 Merge pull request 'Update soapbox-fe base url' (#220) from lou_de_sel/akkoma:lou_de_sel-patch-1 into develop
Reviewed-on: AkkomaGang/akkoma#220
2022-09-19 17:30:04 +00:00
8fe59d495d Update soapbox base url
At some point 'soapbox-pub/soapbox-fe' was moved to 'soapbox-pub/soapbox' and the build url is now updated.
2022-09-18 07:45:30 +00:00
84f8f32ef9 Move remote user interaction changelog entry to correct version
That feature was added in 2022.09, not 2022.08.
2022-09-18 03:21:05 +00:00
55 changed files with 1519 additions and 161 deletions

View file

@ -6,12 +6,12 @@ COPYING
*file
elixir_buildpack.config
test/
instance/
_build
deps
test
benchmarks
docs/site
docker-db
uploads
instance
# Required to get version
!.git

10
.gitignore vendored
View file

@ -17,6 +17,13 @@ secret
/instance
/priv/ssh_keys
vm.args
.cache/
.hex/
.mix/
.psql_history
docker-resources/Dockerfile
docker-resources/Caddyfile
pgdata
# Prevent committing custom emojis
/priv/static/emoji/custom/*
@ -65,3 +72,6 @@ pleroma.iml
# Generated documentation
docs/site
# docker stuff
docker-db

View file

@ -14,6 +14,14 @@ variables:
- stable
- refs/tags/v*
- refs/tags/stable-*
- &on-stable
when:
event:
- push
- tag
branch:
- stable
- refs/tags/stable-*
- &on-point-release
when:
event:
@ -87,7 +95,7 @@ pipeline:
# Canonical amd64
ubuntu22:
image: hexpm/elixir:1.13.4-erlang-25.0.2-ubuntu-jammy-20220428
image: hexpm/elixir:1.13.4-erlang-24.3.4.5-ubuntu-jammy-20220428
<<: *on-release
environment:
MIX_ENV: prod
@ -110,9 +118,11 @@ pipeline:
- export SOURCE=akkoma-ubuntu-jammy.zip
- export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-ubuntu-jammy.zip
- /bin/sh /entrypoint.sh
- export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-amd64-ubuntu-jammy.zip
- /bin/sh /entrypoint.sh
debian-bullseye:
image: elixir:1.13.4
image: hexpm/elixir:1.13.4-erlang-24.3.4.5-debian-bullseye-20220801
<<: *on-release
environment:
MIX_ENV: prod
@ -141,8 +151,8 @@ pipeline:
# Canonical amd64-musl
musl:
image: elixir:1.13.4-alpine
<<: *on-release
image: hexpm/elixir:1.13.4-erlang-24.3.4.5-alpine-3.15.6
<<: *on-stable
environment:
MIX_ENV: prod
commands:
@ -157,7 +167,7 @@ pipeline:
release-musl:
image: akkoma/releaser
<<: *on-release
<<: *on-stable
secrets: *scw-secrets
commands:
- export SOURCE=akkoma-amd64-musl.zip

View file

@ -4,6 +4,32 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Unreleased
## Added
- Officially supported docker release
- Ability to remove followers unilaterally without a block
## Changes
- Follows no longer override domain blocks, a domain block is final
- Deletes are now the lowest priority to publish and will be handled after creates
## 2022.10
### Added
- Ability to sync frontend profiles between clients, with a name attached
- Status card generation will now use the media summary if it is available
### Changed
- Emoji updated to latest 15.0 draft
- **Breaking**: `/api/v1/pleroma/backups` endpoints now requires `read:backups` scope instead of `read:accounts`
- Verify that the signature on posts is not domain blocked, and belongs to the correct user
### Fixed
- OAuthPlug no longer joins with the database every call and uses the user cache
- Undo activities no longer try to look up by ID, and render correctly
- prevent false-errors from meilisearch
## 2022.09
### Added
@ -18,6 +44,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed
- MFM parsing is now done on the backend by a modified version of ilja's parser -> https://akkoma.dev/AkkomaGang/mfm-parser
- InlineQuotePolicy is now on by default
- Enable remote users to interact with posts
### Fixed
- Compatibility with latest meilisearch
@ -44,7 +71,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- amd64 is built for debian stable. Compatible with ubuntu 20.
- ubuntu-jammy is built for... well, ubuntu 22 (LTS)
- amd64-musl is built for alpine 3.16
- Enable remote users to interact with posts
### Fixed
- Updated mastoFE path, for the newer version

View file

@ -1,21 +1,9 @@
FROM elixir:1.13.4-alpine as build
COPY . .
FROM hexpm/elixir:1.13.4-erlang-24.3.4.5-alpine-3.15.6
ENV MIX_ENV=prod
RUN apk add git gcc g++ musl-dev make cmake file-dev &&\
echo "import Config" > config/prod.secret.exs &&\
mix local.hex --force &&\
mix local.rebar --force &&\
mix deps.get --only prod &&\
mkdir release &&\
mix release --path release
FROM alpine:3.16
ARG BUILD_DATE
ARG VCS_REF
ARG HOME=/opt/akkoma
ARG DATA=/var/lib/akkoma
LABEL org.opencontainers.image.title="akkoma" \
org.opencontainers.image.description="Akkoma for Docker" \
@ -26,25 +14,21 @@ LABEL org.opencontainers.image.title="akkoma" \
org.opencontainers.image.revision=$VCS_REF \
org.opencontainers.image.created=$BUILD_DATE
ARG HOME=/opt/akkoma
ARG DATA=/var/lib/akkoma
RUN apk update &&\
apk add exiftool ffmpeg imagemagick libmagic ncurses postgresql-client &&\
adduser --system --shell /bin/false --home ${HOME} akkoma &&\
mkdir -p ${DATA}/uploads &&\
mkdir -p ${DATA}/static &&\
chown -R akkoma ${DATA} &&\
mkdir -p /etc/akkoma &&\
chown -R akkoma /etc/akkoma
USER akkoma
COPY --from=build --chown=akkoma:0 /release ${HOME}
COPY ./config/docker.exs /etc/akkoma/config.exs
COPY ./docker-entrypoint.sh ${HOME}
RUN apk add git gcc g++ musl-dev make cmake file-dev exiftool ffmpeg imagemagick libmagic ncurses postgresql-client
EXPOSE 4000
ENTRYPOINT ["/opt/akkoma/docker-entrypoint.sh"]
ARG UID=1000
ARG GID=1000
ARG UNAME=akkoma
RUN addgroup -g $GID $UNAME
RUN adduser -u $UID -G $UNAME -D -h $HOME $UNAME
WORKDIR /opt/akkoma
USER $UNAME
RUN mix local.hex --force &&\
mix local.rebar --force
CMD ["/opt/akkoma/docker-entrypoint.sh"]

View file

@ -261,7 +261,8 @@
password_reset_token_validity: 60 * 60 * 24,
profile_directory: true,
privileged_staff: false,
local_bubble: []
local_bubble: [],
max_frontend_settings_json_chars: 100_000
config :pleroma, :welcome,
direct_message: [
@ -568,7 +569,10 @@
mute_expire: 5,
search_indexing: 10
],
plugins: [Oban.Plugins.Pruner],
plugins: [
Oban.Plugins.Pruner,
{Oban.Plugins.Reindexer, schedule: "@weekly"}
],
crontab: [
{"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
{"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}
@ -753,9 +757,9 @@
},
"soapbox-fe" => %{
"name" => "soapbox-fe",
"git" => "https://gitlab.com/soapbox-pub/soapbox-fe",
"git" => "https://gitlab.com/soapbox-pub/soapbox",
"build_url" =>
"https://gitlab.com/soapbox-pub/soapbox-fe/-/jobs/artifacts/${ref}/download?job=build-production",
"https://gitlab.com/soapbox-pub/soapbox/-/jobs/artifacts/${ref}/download?job=build-production",
"ref" => "v2.0.0",
"build_dir" => "static"
},

View file

@ -24,11 +24,11 @@
config :web_push_encryption, :vapid_details, subject: "mailto:#{System.get_env("NOTIFY_EMAIL")}"
config :pleroma, :database, rum_enabled: false
config :pleroma, :instance, static_dir: "/var/lib/pleroma/static"
config :pleroma, Pleroma.Uploaders.Local, uploads: "/var/lib/pleroma/uploads"
config :pleroma, :instance, static_dir: "/var/lib/akkoma/static"
config :pleroma, Pleroma.Uploaders.Local, uploads: "/var/lib/akkoma/uploads"
# We can't store the secrets in this file, since this is baked into the docker image
if not File.exists?("/var/lib/pleroma/secret.exs") do
if not File.exists?("/var/lib/akkoma/secret.exs") do
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
{web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1)
@ -52,16 +52,16 @@
web_push_private_key: Base.url_encode64(web_push_private_key, padding: false)
)
File.write("/var/lib/pleroma/secret.exs", secret_file)
File.write("/var/lib/akkoma/secret.exs", secret_file)
end
import_config("/var/lib/pleroma/secret.exs")
import_config("/var/lib/akkoma/secret.exs")
# For additional user config
if File.exists?("/var/lib/pleroma/config.exs"),
do: import_config("/var/lib/pleroma/config.exs"),
if File.exists?("/var/lib/akkoma/config.exs"),
do: import_config("/var/lib/akkoma/config.exs"),
else:
File.write("/var/lib/pleroma/config.exs", """
File.write("/var/lib/akkoma/config.exs", """
import Config
# For additional configuration outside of environmental variables

61
docker-compose.yml Normal file
View file

@ -0,0 +1,61 @@
version: "3.7"
services:
db:
image: akkoma-db:latest
build: ./docker-resources/database
restart: unless-stopped
user: ${DOCKER_USER}
environment: {
# This might seem insecure but is usually not a problem.
# You should leave this at the "akkoma" default.
# The DB is only reachable by containers in the same docker network,
# and is not exposed to the open internet.
#
# If you do change this, remember to update "config.exs".
POSTGRES_DB: akkoma,
POSTGRES_USER: akkoma,
POSTGRES_PASSWORD: akkoma,
}
env_file:
- .env
volumes:
- type: bind
source: ./pgdata
target: /var/lib/postgresql/data
akkoma:
image: akkoma:latest
build: .
restart: unless-stopped
env_file:
- .env
links:
- db
ports: [
# Uncomment/Change port mappings below as needed.
# The left side is your host machine, the right one is the akkoma container.
# You can prefix the left side with an ip.
# Webserver (for reverse-proxies outside of docker)
# If you use a dockerized proxy, you can leave this commented
# and use a container link instead.
"127.0.0.1:4000:4000",
]
volumes:
- .:/opt/akkoma
# Uncomment the following if you want to use a reverse proxy
#proxy:
# image: caddy:2-alpine
# restart: unless-stopped
# links:
# - akkoma
# ports: [
# "443:443",
# "80:80"
# ]
# volumes:
# - ./docker-resources/Caddyfile:/etc/caddy/Caddyfile
# - ./caddy-data:/data
# - ./caddy-config:/config

View file

@ -8,7 +8,7 @@ while ! pg_isready -U ${DB_USER:-pleroma} -d postgres://${DB_HOST:-db}:5432/${DB
done
echo "-- Running migrations..."
$HOME/bin/pleroma_ctl migrate
mix ecto.migrate
echo "-- Starting!"
exec $HOME/bin/pleroma start
mix phx.server

View file

@ -0,0 +1,14 @@
# default docker Caddyfile config for Akkoma
#
# Simple installation instructions:
# 1. Replace 'example.tld' with your instance's domain wherever it appears.
example.tld {
log {
output file /var/log/caddy/akkoma.log
}
encode gzip
reverse_proxy akkoma:4000
}

4
docker-resources/build.sh Executable file
View file

@ -0,0 +1,4 @@
#!/bin/sh
docker-compose build --build-arg UID=$(id -u) --build-arg GID=$(id -g) akkoma
docker-compose build --build-arg UID=$(id -u) --build-arg GID=$(id -g) db

View file

@ -0,0 +1,10 @@
FROM postgres:14-alpine
ARG UID=1000
ARG GID=1000
ARG UNAME=akkoma
RUN addgroup -g $GID $UNAME
RUN adduser -u $UID -G $UNAME -D -h $HOME $UNAME
USER akkoma

View file

@ -0,0 +1,4 @@
MIX_ENV=prod
DB_NAME=akkoma
DB_USER=akkoma
DB_PASS=akkoma

3
docker-resources/manage.sh Executable file
View file

@ -0,0 +1,3 @@
#!/bin/sh
docker-compose run --rm akkoma $@

View file

@ -14,6 +14,10 @@ su akkoma -s $SHELL -lc "./bin/pleroma_ctl update"
su akkoma -s $SHELL -lc "./bin/pleroma_ctl migrate"
```
If you selected an alternate flavour on installation,
you _may_ need to specify `--flavour`, in the same way as
[when installing](../../installation/otp_en#detecting-flavour).
## For from source installations (using git)
1. Go to the working directory of Akkoma (default is `/opt/akkoma`)

View file

@ -14,11 +14,12 @@ apt -yq install tor
**WARNING:** Onion instances not using a Tor version supporting V3 addresses will not be able to federate with you.
Create the hidden service for your Akkoma instance in `/etc/tor/torrc`:
Create the hidden service for your Akkoma instance in `/etc/tor/torrc`, with an HTTP tunnel:
```
HiddenServiceDir /var/lib/tor/akkoma_hidden_service/
HiddenServicePort 80 127.0.0.1:8099
HiddenServiceVersion 3 # Remove if Tor version is below 0.3 ( tor --version )
HTTPTunnelPort 9080
```
Restart Tor to generate an adress:
```
@ -35,7 +36,7 @@ Next, edit your Akkoma config.
If running in prod, navigate to your Akkoma directory, edit `config/prod.secret.exs`
and append this line:
```
config :pleroma, :http, proxy_url: {:socks5, :localhost, 9050}
config :pleroma, :http, proxy_url: "http://localhost:9080"
```
In your Akkoma directory, assuming you're running prod,
run the following:

View file

@ -141,8 +141,7 @@ You then need to set the URL and authentication credentials if relevant.
### Initial indexing
After setting up the configuration, you'll want to index all of your already existsing posts. Only public posts are indexed. You'll only
have to do it one time, but it might take a while, depending on the amount of posts your instance has seen.
After setting up the configuration, you'll want to index all of your already existsing posts. You'll only have to do it one time, but it might take a while, depending on the amount of posts your instance has seen.
The sequence of actions is as follows:

View file

@ -7,6 +7,20 @@ It actually consists of two components: a backend, named simply Akkoma, and a us
It's part of what we call the fediverse, a federated network of instances which speak common protocols and can communicate with each other.
One account on an instance is enough to talk to the entire fediverse!
## Community Channels
### IRC
For support or general questions, pop over to #akkoma and #akkoma-dev at [irc.akkoma.dev](https://irc.akkoma.dev) (port 6697, SSL)
### Discourse
For more general meta-discussion, for example discussion of potential future features, head on over to [meta.akkoma.dev](https://meta.akkoma.dev)
### Dev diaries and release notifications
will be posted via [@akkoma@ihba](https://ihatebeinga.live/users/akkoma)
## How can I use it?
Akkoma instances are already widely deployed, a list can be found at <https://the-federation.info/pleroma> and <https://fediverse.network/pleroma>.
@ -26,3 +40,4 @@ Just add a "/web" after your instance url (e.g. <https://pleroma.soykaf.com/web>
The Mastodon interface is from the Glitch-soc fork. For more information on the Mastodon interface you can check the [Mastodon](https://docs.joinmastodon.org/) and [Glitch-soc](https://glitch-soc.github.io/docs/) documentation.
Remember, what you see is only the frontend part of Mastodon, the backend is still Akkoma.

View file

@ -0,0 +1,161 @@
# Installing in Docker
## Installation
This guide will show you how to get akkoma working in a docker container,
if you want isolation, or if you run a distribution not supported by the OTP
releases.
If you want to migrate from or OTP to docker, check out [the migration guide](./migrating_to_docker_en.md).
### Prepare the system
* Install docker and docker-compose
* [Docker](https://docs.docker.com/engine/install/)
* [Docker-compose](https://docs.docker.com/compose/install/)
* This will usually just be a repository installation and a package manager invocation.
* Clone the akkoma repository
* `git clone https://akkoma.dev/AkkomaGang/akkoma.git -b stable`
* `cd akkoma`
### Set up basic configuration
```bash
cp docker-resources/env.example .env
echo "DOCKER_USER=$(id -u):$(id -g)" >> .env
```
This probably won't need to be changed, it's only there to set basic environment
variables for the docker-compose file.
### Building the container
The container provided is a thin wrapper around akkoma's dependencies,
it does not contain the code itself. This is to allow for easy updates
and debugging if required.
```bash
./docker-resources/build.sh
```
This will generate a container called `akkoma` which we can use
in our compose environment.
### Generating your instance
```bash
mkdir pgdata
./docker-resources/manage.sh mix deps.get
./docker-resources/manage.sh mix compile
./docker-resources/manage.sh mix pleroma.instance gen
```
This will ask you a few questions - the defaults are fine for most things,
the database hostname is `db`, and you will want to set the ip to `0.0.0.0`.
Now we'll want to copy over the config it just created
```bash
cp config/generated_config.exs config/prod.secret.exs
```
### Setting up the database
We need to run a few commands on the database container, this isn't too bad
```bash
docker-compose run --rm --user akkoma -d db
# Note down the name it gives here, it will be something like akkoma_db_run
docker-compose run --rm akkoma psql -h db -U akkoma -f config/setup_db.psql
docker stop akkoma_db_run # Replace with the name you noted down
```
Now we can actually run our migrations
```bash
./docker-resources/manage.sh mix ecto.migrate
# this will recompile your files at the same time, since we changed the config
```
### Start the server
We're going to run it in the foreground on the first run, just to make sure
everything start up.
```bash
docker-compose up
```
If everything went well, you should be able to access your instance at http://localhost:4000
You can `ctrl-c` out of the docker-compose now to shutdown the server.
### Running in the background
```bash
docker-compose up -d
```
### Create your first user
If your instance is up and running, you can create your first user with administrative rights with the following task:
```shell
./docker-resources/manage.sh mix pleroma.user new MY_USERNAME MY_EMAIL@SOMEWHERE --admin
```
And follow the prompts
### Reverse proxies
This is a tad more complex in docker than on the host itself. It
You've got two options.
#### Running caddy in a container
This is by far the easiest option. It'll handle HTTPS and all that for you.
```bash
mkdir caddy-data
mkdir caddy-config
cp docker-resources/Caddyfile.example docker-resources/Caddyfile
```
Then edit the TLD in your caddyfile to the domain you're serving on.
Uncomment the `caddy` section in the docker-compose file,
then run `docker-compose up -d` again.
#### Running a reverse proxy on the host
If you want, you can also run the reverse proxy on the host. This is a bit more complex, but it's also more flexible.
Follow the guides for source install for your distribution of choice, or adapt
as needed. Your standard setup can be found in the [Debian Guide](../debian_based_en/#nginx)
### You're done!
All that's left is to set up your frontends.
The standard from-source commands will apply to you, just make sure you
prefix them with `./docker-resources/manage.sh`!
{! installation/frontends.include !}
### Updating Docker Installs
```bash
git pull
./docker-resources/build.sh
./docker-resources/manage.sh mix deps.get
./docker-resources/manage.sh mix compile
./docker-resources/manage.sh mix ecto.migrate
docker-compose restart akkoma db
```
#### Further reading
{! installation/further_reading.include !}
{! support.include !}

View file

@ -21,5 +21,11 @@ For most installations, the following will suffice:
mix pleroma.frontend install admin-fe --ref stable
```
=== "Docker"
```sh
./docker-resources/manage.sh mix pleroma.frontend install pleroma-fe --ref stable
./docker-resources/manage.sh mix pleroma.frontend install admin-fe --ref stable
```
For more customised installations, refer to [Frontend Management](../../configuration/frontend_management)

View file

@ -0,0 +1,158 @@
# Migrating to a Docker Installation
If you for any reason wish to migrate a source or OTP install to a docker one,
this guide is for you.
You have a few options - your major one will be whether you want to keep your
reverse-proxy setup from before.
You probably should, in the first instance.
### Prepare the system
* Install docker and docker-compose
* [Docker](https://docs.docker.com/engine/install/)
* [Docker-compose](https://docs.docker.com/compose/install/)
* This will usually just be a repository installation and a package manager invocation.
=== "Source"
```bash
git pull
```
=== "OTP"
Clone the akkoma repository
```bash
git clone https://akkoma.dev/AkkomaGang/akkoma.git -b stable
cd akkoma
```
### Back up your old database
Change the database name as needed
```bash
pg_dump -d akkoma_prod --format c > akkoma_backup.sql
```
### Getting your static files in the right place
This will vary by every installation. Copy your `instance` directory to `instance/` in
the akkoma source directory - this is where the docker container will look for it.
For *most* from-source installs it'll already be there.
And the same with `uploads`, make sure your uploads (if you have them on disk) are
located at `uploads/` in the akkoma source directory.
If you have them on a different disk, you will need to mount that disk into the docker-compose file,
with an entry that looks like this:
```yaml
akkoma:
volumes:
- .:/opt/akkoma # This should already be there
- type: bind
source: /path/to/your/uploads
target: /opt/akkoma/uploads
```
### Set up basic configuration
```bash
cp docker-resources/env.example .env
echo "DOCKER_USER=$(id -u):$(id -g)" >> .env
```
This probably won't need to be changed, it's only there to set basic environment
variables for the docker-compose file.
=== "From source"
You probably won't need to change your config. Provided your `config/prod.secret.exs` file
is still there, you're all good.
=== "OTP"
```bash
cp /etc/akkoma/config.exs config/prod.secret.exs
```
**BOTH**
Set the following config in `config/prod.secret.exs`:
```elixir
config :pleroma, Pleroma.Web.Endpoint,
...,
http: [ip: {0, 0, 0, 0}, port: 4000]
config :pleroma, Pleroma.Repo,
...,
username: "akkoma",
password: "akkoma",
database: "akkoma",
hostname: "db"
```
### Building the container
The container provided is a thin wrapper around akkoma's dependencies,
it does not contain the code itself. This is to allow for easy updates
and debugging if required.
```bash
./docker-resources/build.sh
```
This will generate a container called `akkoma` which we can use
in our compose environment.
### Setting up the docker resources
```bash
# These won't exist if you're migrating from OTP
rm -rf deps
rm -rf _build
```
```bash
mkdir pgdata
./docker-resources/manage.sh mix deps.get
./docker-resources/manage.sh mix compile
```
### Setting up the database
Now we can import our database to the container.
```bash
docker-compose run --rm --user akkoma -d db
docker-compose run --rm akkoma pg_restore -v -U akkoma -j $(grep -c ^processor /proc/cpuinfo) -d akkoma -h db akkoma_backup.sql
```
### Reverse proxies
If you're just reusing your old proxy, you may have to uncomment the line in
the docker-compose file under `ports`. You'll find it.
Otherwise, you can use the same setup as the [docker installation guide](./docker_en.md#reverse-proxies).
### Let's go
```bash
docker-compose up -d
```
You should now be at the same point as you were before, but with a docker install.
{! installation/frontends.include !}
See the [docker installation guide](./docker_en.md) for more information on how to
update.
#### Further reading
{! installation/further_reading.include !}
{! support.include !}

View file

@ -19,12 +19,12 @@ This is a little more complex than it used to be (thanks ubuntu)
Use the following mapping to figure out your flavour:
| distribution | flavour |
| ------------- | ------------ |
| debian stable | amd64 |
| ubuntu focal | amd64 |
| ubuntu jammy | ubuntu-jammy |
| alpine | amd64-musl |
| distribution | flavour | available branches |
| ------------- | ------------------ | ------------------- |
| debian stable | amd64 | develop, stable |
| ubuntu focal | amd64 | develop, stable |
| ubuntu jammy | amd64-ubuntu-jammy | develop, stable |
| alpine | amd64-musl | stable |
Other similar distributions will _probably_ work, but if it is not listed above, there is no official
support.

View file

@ -538,6 +538,12 @@ def run(["fix_follow_state", local_user, remote_user]) do
end
end
def run(["convert_id", id]) do
{:ok, uuid} = FlakeId.Ecto.Type.dump(id)
{:ok, raw_id} = Ecto.UUID.load(uuid)
shell_info(raw_id)
end
defp refetch_public_keys(query) do
query
|> Pleroma.Repo.chunk_stream(50, :batches)

View file

@ -368,9 +368,15 @@ def following_requests_for_actor(%User{ap_id: ap_id}) do
end
def restrict_deactivated_users(query) do
deactivated_users_query = from(u in User.Query.build(%{deactivated: true}), select: u.ap_id)
from(activity in query, where: activity.actor not in subquery(deactivated_users_query))
query
|> join(
:inner_lateral,
[activity],
active in fragment(
"SELECT is_active from users WHERE ap_id = ? AND is_active = TRUE",
activity.actor
)
)
end
defdelegate search(user, query, options \\ []), to: Pleroma.Search.DatabaseSearch

View file

@ -0,0 +1,100 @@
defmodule Pleroma.Akkoma.FrontendSettingsProfile do
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query
alias Pleroma.Repo
alias Pleroma.Config
alias Pleroma.User
@primary_key false
schema "user_frontend_setting_profiles" do
belongs_to(:user, Pleroma.User, primary_key: true, type: FlakeId.Ecto.CompatType)
field(:frontend_name, :string, primary_key: true)
field(:profile_name, :string, primary_key: true)
field(:settings, :map)
field(:version, :integer)
timestamps()
end
def changeset(%__MODULE__{} = struct, attrs) do
struct
|> cast(attrs, [:user_id, :frontend_name, :profile_name, :settings, :version])
|> validate_required([:user_id, :frontend_name, :profile_name, :settings, :version])
|> validate_length(:frontend_name, min: 1, max: 255)
|> validate_length(:profile_name, min: 1, max: 255)
|> validate_version(struct)
|> validate_number(:version, greater_than: 0)
|> validate_settings_length(Config.get([:instance, :max_frontend_settings_json_chars]))
end
def create_or_update(%User{} = user, frontend_name, profile_name, settings, version) do
struct =
case get_by_user_and_frontend_name_and_profile_name(user, frontend_name, profile_name) do
nil ->
%__MODULE__{}
%__MODULE__{} = profile ->
profile
end
struct
|> changeset(%{
user_id: user.id,
frontend_name: frontend_name,
profile_name: profile_name,
settings: settings,
version: version
})
|> Repo.insert_or_update()
end
def get_all_by_user_and_frontend_name(%User{id: user_id}, frontend_name) do
Repo.all(
from(p in __MODULE__, where: p.user_id == ^user_id and p.frontend_name == ^frontend_name)
)
end
def get_by_user_and_frontend_name_and_profile_name(
%User{id: user_id},
frontend_name,
profile_name
) do
Repo.one(
from(p in __MODULE__,
where:
p.user_id == ^user_id and p.frontend_name == ^frontend_name and
p.profile_name == ^profile_name
)
)
end
def delete_profile(profile) do
Repo.delete(profile)
end
defp validate_settings_length(
%Ecto.Changeset{changes: %{settings: settings}} = changeset,
max_length
) do
settings_json = Jason.encode!(settings)
if String.length(settings_json) > max_length do
add_error(changeset, :settings, "is too long")
else
changeset
end
end
defp validate_version(changeset, %{version: nil}), do: changeset
defp validate_version(%Ecto.Changeset{changes: %{version: version}} = changeset, %{
version: prev_version
}) do
if version != prev_version + 1 do
add_error(changeset, :version, "must be incremented by 1")
else
changeset
end
end
end

View file

@ -240,30 +240,6 @@ def find(following_relationships, follower, following) do
end)
end
@doc """
For a query with joined activity,
keeps rows where activity's actor is followed by user -or- is NOT domain-blocked by user.
"""
def keep_following_or_not_domain_blocked(query, user) do
where(
query,
[_, activity],
fragment(
# "(actor's domain NOT in domain_blocks) OR (actor IS in followed AP IDs)"
"""
NOT (substring(? from '.*://([^/]*)') = ANY(?)) OR
? = ANY(SELECT ap_id FROM users AS u INNER JOIN following_relationships AS fr
ON u.id = fr.following_id WHERE fr.follower_id = ? AND fr.state = ?)
""",
activity.actor,
^user.domain_blocks,
activity.actor,
^User.binary_id(user.id),
^accept_state_code()
)
)
end
defp validate_not_self_relationship(%Changeset{} = changeset) do
changeset
|> validate_follower_id_following_id_inequality()

View file

@ -138,7 +138,24 @@ defp exclude_blocked(query, user, opts) do
query
|> where([n, a], a.actor not in ^blocked_ap_ids)
|> FollowingRelationship.keep_following_or_not_domain_blocked(user)
|> restrict_domain_blocked(user)
end
defp restrict_domain_blocked(query, user) do
where(
query,
[_, activity],
fragment(
# "(actor's domain NOT in domain_blocks)"
"""
NOT (
substring(? from '.*://([^/]*)') = ANY(?)
)
""",
activity.actor,
^user.domain_blocks
)
)
end
defp exclude_blockers(query, user) do

View file

@ -153,7 +153,7 @@ def add_to_index(activity) do
)
with {:ok, res} <- result,
true <- Map.has_key?(res, "uid") do
true <- Map.has_key?(res, "taskUid") do
# Do nothing
else
_ ->

View file

@ -165,6 +165,8 @@ defmodule Pleroma.User do
has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
has_many(:frontend_profiles, Pleroma.Akkoma.FrontendSettingsProfile)
for {relationship_type,
[
{outgoing_relation, outgoing_relation_target},

View file

@ -108,8 +108,8 @@ defp blocked_instances do
Config.get([:mrf_simple, :reject], [])
end
defp should_federate?(inbox) do
%{host: host} = URI.parse(inbox)
def should_federate?(url) do
%{host: host} = URI.parse(url)
quarantined_instances =
blocked_instances()

View file

@ -323,8 +323,6 @@ def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object,
end
if result == :ok do
Notification.create_notifications(object)
# Only remove from index when deleting actual objects, not users or anything else
with %Pleroma.Object{} <- deleted_object do
Pleroma.Search.remove_from_index(deleted_object)

View file

@ -0,0 +1,96 @@
defmodule Pleroma.Web.AkkomaAPI.FrontendSettingsController do
use Pleroma.Web, :controller
alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Akkoma.FrontendSettingsProfile
@unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []}
plug(
OAuthScopesPlug,
%{@unauthenticated_access | scopes: ["read:accounts"]}
when action in [
:list_profiles,
:get_profile
]
)
plug(
OAuthScopesPlug,
%{@unauthenticated_access | scopes: ["write:accounts"]}
when action in [
:update_profile,
:delete_profile
]
)
plug(Pleroma.Web.ApiSpec.CastAndValidate)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.FrontendSettingsOperation
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
@doc "GET /api/v1/akkoma/frontend_settings/:frontend_name/:profile_name"
def get_profile(conn, %{frontend_name: frontend_name, profile_name: profile_name}) do
with %FrontendSettingsProfile{} = profile <-
FrontendSettingsProfile.get_by_user_and_frontend_name_and_profile_name(
conn.assigns.user,
frontend_name,
profile_name
) do
conn
|> json(%{
settings: profile.settings,
version: profile.version
})
else
nil -> {:error, :not_found}
end
end
@doc "GET /api/v1/akkoma/frontend_settings/:frontend_name"
def list_profiles(conn, %{frontend_name: frontend_name}) do
with profiles <-
FrontendSettingsProfile.get_all_by_user_and_frontend_name(
conn.assigns.user,
frontend_name
),
data <-
Enum.map(profiles, fn profile ->
%{name: profile.profile_name, version: profile.version}
end) do
json(conn, data)
end
end
@doc "DELETE /api/v1/akkoma/frontend_settings/:frontend_name/:profile_name"
def delete_profile(conn, %{frontend_name: frontend_name, profile_name: profile_name}) do
with %FrontendSettingsProfile{} = profile <-
FrontendSettingsProfile.get_by_user_and_frontend_name_and_profile_name(
conn.assigns.user,
frontend_name,
profile_name
),
{:ok, _} <- FrontendSettingsProfile.delete_profile(profile) do
json(conn, %{deleted: "ok"})
else
nil -> {:error, :not_found}
end
end
@doc "PUT /api/v1/akkoma/frontend_settings/:frontend_name/:profile_name"
def update_profile(%{body_params: %{settings: settings, version: version}} = conn, %{
frontend_name: frontend_name,
profile_name: profile_name
}) do
with {:ok, profile} <-
FrontendSettingsProfile.create_or_update(
conn.assigns.user,
frontend_name,
profile_name,
settings,
version
) do
conn
|> json(profile.settings)
end
end
end

View file

@ -334,6 +334,22 @@ def unblock_operation do
}
end
def remove_from_followers_operation do
%Operation{
tags: ["Account actions"],
summary: "Remove from followers",
operationId: "AccountController.remove_from_followers",
security: [%{"oAuth" => ["follow", "write:follows"]}],
description: "Remove the given account from followers",
parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
responses: %{
200 => Operation.response("Relationship", "application/json", AccountRelationship),
400 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError)
}
}
end
def note_operation do
%Operation{
tags: ["Account actions"],

View file

@ -0,0 +1,133 @@
defmodule Pleroma.Web.ApiSpec.FrontendSettingsOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
import Pleroma.Web.ApiSpec.Helpers
@spec open_api_operation(atom) :: Operation.t()
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
@spec list_profiles_operation() :: Operation.t()
def list_profiles_operation() do
%Operation{
tags: ["Retrieve frontend setting profiles"],
summary: "Frontend Settings Profiles",
description: "List frontend setting profiles",
operationId: "AkkomaAPI.FrontendSettingsController.list_profiles",
parameters: [frontend_name_param()],
security: [%{"oAuth" => ["read:accounts"]}],
responses: %{
200 =>
Operation.response("Profiles", "application/json", %Schema{
type: :array,
items: %Schema{
type: :object,
properties: %{
name: %Schema{type: :string},
version: %Schema{type: :integer}
}
}
})
}
}
end
@spec get_profile_operation() :: Operation.t()
def get_profile_operation() do
%Operation{
tags: ["Retrieve frontend setting profile"],
summary: "Frontend Settings Profile",
description: "Get frontend setting profile",
operationId: "AkkomaAPI.FrontendSettingsController.get_profile",
security: [%{"oAuth" => ["read:accounts"]}],
parameters: [frontend_name_param(), profile_name_param()],
responses: %{
200 =>
Operation.response("Profile", "application/json", %Schema{
type: :object,
properties: %{
"version" => %Schema{type: :integer},
"settings" => %Schema{type: :object, additionalProperties: true}
}
}),
404 => Operation.response("Not Found", "application/json", %Schema{type: :object})
}
}
end
@spec delete_profile_operation() :: Operation.t()
def delete_profile_operation() do
%Operation{
tags: ["Delete frontend setting profile"],
summary: "Delete frontend Settings Profile",
description: "Delete frontend setting profile",
operationId: "AkkomaAPI.FrontendSettingsController.delete_profile",
security: [%{"oAuth" => ["write:accounts"]}],
parameters: [frontend_name_param(), profile_name_param()],
responses: %{
200 => Operation.response("Empty", "application/json", %Schema{type: :object}),
404 => Operation.response("Not Found", "application/json", %Schema{type: :object})
}
}
end
@spec update_profile_operation() :: Operation.t()
def update_profile_operation() do
%Operation{
tags: ["Update frontend setting profile"],
summary: "Frontend Settings Profile",
description: "Update frontend setting profile",
operationId: "AkkomaAPI.FrontendSettingsController.update_profile_operation",
security: [%{"oAuth" => ["write:accounts"]}],
parameters: [frontend_name_param(), profile_name_param()],
requestBody: profile_body_param(),
responses: %{
200 => Operation.response("Settings", "application/json", %Schema{type: :object}),
422 => Operation.response("Invalid", "application/json", %Schema{type: :object})
}
}
end
def frontend_name_param do
Operation.parameter(:frontend_name, :path, :string, "Frontend name",
example: "pleroma-fe",
required: true
)
end
def profile_name_param do
Operation.parameter(:profile_name, :path, :string, "Profile name",
example: "mobile",
required: true
)
end
def profile_body_param do
request_body(
"Settings",
%Schema{
title: "Frontend Setting Profile",
type: :object,
required: [:version, :settings],
properties: %{
version: %Schema{
type: :integer,
description: "Version of the profile, must increment by 1 each time",
example: 1
},
settings: %Schema{
type: :object,
description: "Settings of the profile",
example: %{
theme: "dark",
locale: "en"
}
}
}
},
required: true
)
end
end

View file

@ -16,7 +16,7 @@ def index_operation do
%Operation{
tags: ["Backups"],
summary: "List backups",
security: [%{"oAuth" => ["read:account"]}],
security: [%{"oAuth" => ["read:backups"]}],
operationId: "PleromaAPI.BackupController.index",
responses: %{
200 =>
@ -37,7 +37,7 @@ def create_operation do
%Operation{
tags: ["Backups"],
summary: "Create a backup",
security: [%{"oAuth" => ["read:account"]}],
security: [%{"oAuth" => ["read:backups"]}],
operationId: "PleromaAPI.BackupController.create",
responses: %{
200 =>

View file

@ -53,12 +53,19 @@ def publish(%{data: %{"object" => object}} = activity) when is_binary(object) do
@impl true
def publish(%{data: %{"object" => object}} = activity) when is_map(object) or is_list(object) do
PublisherWorker.enqueue("publish", %{
"activity_id" => activity.id,
"object_data" => Jason.encode!(object)
})
PublisherWorker.enqueue(
"publish",
%{
"activity_id" => activity.id,
"object_data" => Jason.encode!(object)
},
priority: publish_priority(activity)
)
end
defp publish_priority(%{type: "Delete"}), do: 3
defp publish_priority(_), do: 0
# Job Worker Callbacks
@spec perform(atom(), module(), any()) :: {:ok, any()} | {:error, any()}

View file

@ -76,15 +76,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
plug(
OAuthScopesPlug,
%{scopes: ["follow", "write:follows"]} when action in [:follow_by_uri, :follow, :unfollow]
%{scopes: ["follow", "write:follows"]}
when action in [:follow_by_uri, :follow, :unfollow, :remove_from_followers]
)
plug(OAuthScopesPlug, %{scopes: ["follow", "read:mutes"]} when action == :mutes)
plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute])
@relationship_actions [:follow, :unfollow]
@needs_account ~W(followers following lists follow unfollow mute unmute block unblock note)a
@relationship_actions [:follow, :unfollow, :remove_from_followers]
@needs_account ~W(followers following lists follow unfollow mute unmute block unblock note remove_from_followers)a
plug(
RateLimiter,
@ -447,6 +448,20 @@ def note(
end
end
@doc "POST /api/v1/accounts/:id/remove_from_followers"
def remove_from_followers(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do
{:error, "Can not unfollow yourself"}
end
def remove_from_followers(%{assigns: %{user: followed, account: follower}} = conn, _params) do
with {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
render(conn, "relationship.json", user: followed, target: follower)
else
nil ->
render_error(conn, :not_found, "Record not found")
end
end
@doc "POST /api/v1/follows"
def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do
case User.get_cached_by_nickname(uri) do

View file

@ -9,7 +9,7 @@ defmodule Pleroma.Web.PleromaAPI.BackupController do
alias Pleroma.Web.Plugs.OAuthScopesPlug
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(OAuthScopesPlug, %{scopes: ["read:accounts"]} when action in [:index, :create])
plug(OAuthScopesPlug, %{scopes: ["read:backups"]} when action in [:index, :create])
plug(Pleroma.Web.ApiSpec.CastAndValidate)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaBackupOperation

View file

@ -19,6 +19,7 @@ def call(%{assigns: %{user: %User{}}} = conn, _opts), do: conn
def call(%{assigns: %{valid_signature: true}, params: %{"actor" => actor}} = conn, _opts) do
with actor_id <- Utils.get_ap_id(actor),
{:user, %User{} = user} <- {:user, user_from_key_id(conn)},
{:federate, true} <- {:federate, should_federate?(user)},
{:user_match, true} <- {:user_match, user.ap_id == actor_id} do
conn
|> assign(:user, user)
@ -27,33 +28,70 @@ def call(%{assigns: %{valid_signature: true}, params: %{"actor" => actor}} = con
{:user_match, false} ->
Logger.debug("Failed to map identity from signature (payload actor mismatch)")
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{inspect(actor)}")
assign(conn, :valid_signature, false)
conn
|> assign(:valid_signature, false)
# remove me once testsuite uses mapped capabilities instead of what we do now
{:user, nil} ->
Logger.debug("Failed to map identity from signature (lookup failure)")
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{actor}")
conn
|> assign(:valid_signature, false)
{:federate, false} ->
Logger.debug("Identity from signature is instance blocked")
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{actor}")
conn
|> assign(:valid_signature, false)
end
end
# no payload, probably a signed fetch
def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
with %User{} = user <- user_from_key_id(conn) do
with %User{} = user <- user_from_key_id(conn),
{:federate, true} <- {:federate, should_federate?(user)} do
conn
|> assign(:user, user)
|> AuthHelper.skip_oauth()
else
{:federate, false} ->
Logger.debug("Identity from signature is instance blocked")
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}")
conn
|> assign(:valid_signature, false)
nil ->
Logger.debug("Failed to map identity from signature (lookup failure)")
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}")
only_permit_user_routes(conn)
_ ->
Logger.debug("Failed to map identity from signature (no payload actor mismatch)")
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}")
assign(conn, :valid_signature, false)
conn
|> assign(:valid_signature, false)
end
end
# no signature at all
def call(conn, _opts), do: conn
defp only_permit_user_routes(%{path_info: ["users", _]} = conn) do
conn
|> assign(:limited_ap, true)
end
defp only_permit_user_routes(conn) do
conn
|> assign(:valid_signature, false)
end
defp key_id_from_conn(conn) do
with %{"keyId" => key_id} <- HTTPSignatures.signature_for_conn(conn),
{:ok, ap_id} <- Signature.key_id_to_actor_id(key_id) do
@ -73,4 +111,14 @@ defp user_from_key_id(conn) do
nil
end
end
defp should_federate?(%User{ap_id: ap_id}), do: should_federate?(ap_id)
defp should_federate?(ap_id) do
if Pleroma.Config.get([:activitypub, :authorized_fetch_mode], false) do
Pleroma.Web.ActivityPub.Publisher.should_federate?(ap_id)
else
true
end
end
end

View file

@ -466,6 +466,26 @@ defmodule Pleroma.Web.Router do
scope "/api/v1/akkoma", Pleroma.Web.AkkomaAPI do
pipe_through(:authenticated_api)
get("/translation/languages", TranslationController, :languages)
get("/frontend_settings/:frontend_name", FrontendSettingsController, :list_profiles)
get(
"/frontend_settings/:frontend_name/:profile_name",
FrontendSettingsController,
:get_profile
)
put(
"/frontend_settings/:frontend_name/:profile_name",
FrontendSettingsController,
:update_profile
)
delete(
"/frontend_settings/:frontend_name/:profile_name",
FrontendSettingsController,
:delete_profile
)
end
scope "/api/v1", Pleroma.Web.MastodonAPI do
@ -489,6 +509,7 @@ defmodule Pleroma.Web.Router do
post("/accounts/:id/mute", AccountController, :mute)
post("/accounts/:id/unmute", AccountController, :unmute)
post("/accounts/:id/note", AccountController, :note)
post("/accounts/:id/remove_from_followers", AccountController, :remove_from_followers)
get("/conversations", ConversationController, :index)
post("/conversations/:id/read", ConversationController, :mark_as_read)

View file

@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
def project do
[
app: :pleroma,
version: version("3.2.0"),
version: version("3.3.1"),
elixir: "~> 1.12",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
@ -120,7 +120,7 @@ defp deps do
{:phoenix_pubsub, "~> 2.1"},
{:phoenix_ecto, "~> 4.4"},
{:ecto_enum, "~> 1.4"},
{:ecto_sql, "~> 3.8.3"},
{:ecto_sql, "~> 3.9.0"},
{:postgrex, ">= 0.16.3"},
{:oban, "~> 2.12.1"},
{:gettext,

View file

@ -26,10 +26,10 @@
"earmark": {:hex, :earmark, "1.4.26", "f0e3c3d5c278a6d448ad8c27ab0ecdec9c57a7710553138c56af220a6330a4fd", [:mix], [{:earmark_parser, "~> 1.4.26", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "e1231882b56bece0692af33f0959f06c9cd580c2dc2ecb1dc9f16f2750fa78c5"},
"earmark_parser": {:hex, :earmark_parser, "1.4.26", "f4291134583f373c7d8755566122908eb9662df4c4b63caa66a0eabe06569b0a", [:mix], [], "hexpm", "48d460899f8a0c52c5470676611c01f64f3337bad0b26ddab43648428d94aabc"},
"eblurhash": {:hex, :eblurhash, "1.2.2", "7da4255aaea984b31bb71155f673257353b0e0554d0d30dcf859547e74602582", [:rebar3], [], "hexpm", "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c"},
"ecto": {:hex, :ecto, "3.8.4", "e06b8b87e62b27fea17fd2ff6041572ddd10339fd16cdf58446e402c6c90a74b", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f9244288b8d42db40515463a008cf3f4e0e564bb9c249fe87bf28a6d79fe82d4"},
"ecto": {:hex, :ecto, "3.9.1", "67173b1687afeb68ce805ee7420b4261649d5e2deed8fe5550df23bab0bc4396", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c80bb3d736648df790f7f92f81b36c922d9dd3203ca65be4ff01d067f54eb304"},
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
"ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.4", "5d43fd088d39a158c860b17e8d210669587f63ec89ea122a4654861c8c6e2db4", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.15.7", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "311db02f1b772e3d0dc7f56a05044b5e1499d78ed6abf38885e1ca70059449e5"},
"ecto_sql": {:hex, :ecto_sql, "3.8.3", "a7d22c624202546a39d615ed7a6b784580391e65723f2d24f65941b4dd73d471", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.8.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0 or ~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "348cb17fb9e6daf6f251a87049eafcb57805e2892e5e6a0f5dea0985d367329b"},
"ecto_sql": {:hex, :ecto_sql, "3.9.0", "2bb21210a2a13317e098a420a8c1cc58b0c3421ab8e3acfa96417dab7817918c", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a8f3f720073b8b1ac4c978be25fa7960ed7fd44997420c304a4a2e200b596453"},
"elasticsearch": {:git, "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", "6cd946f75f6ab9042521a009d1d32d29a90113ca", [ref: "main"]},
"elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"},
"eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"},
@ -56,7 +56,7 @@
"httpoison": {:hex, :httpoison, "1.8.1", "df030d96de89dad2e9983f92b0c506a642d4b1f4a819c96ff77d12796189c63e", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "35156a6d678d6d516b9229e208942c405cf21232edd632327ecfaf4fd03e79e0"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"},
"jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"},
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
"joken": {:hex, :joken, "2.5.0", "09be497d804b8115eb6f07615cef2e60c2a1008fb89dc0aef0d4c4b4609b99aa", [:mix], [{:jose, "~> 1.11.2", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "22b25c89617c5ed8ca7b31026340a25ea0f9ca7160f9706b79be9ed81fdf74e7"},
"jose": {:hex, :jose, "1.11.2", "f4c018ccf4fdce22c71e44d471f15f723cb3efab5d909ab2ba202b5bf35557b3", [:mix, :rebar3], [], "hexpm", "98143fbc48d55f3a18daba82d34fe48959d44538e9697c08f34200fa5f0947d2"},
"jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
@ -94,7 +94,7 @@
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
"postgrex": {:hex, :postgrex, "0.16.3", "fac79a81a9a234b11c44235a4494d8565303fa4b9147acf57e48978a074971db", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "aeaae1d2d1322da4e5fe90d241b0a564ce03a3add09d7270fb85362166194590"},
"postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"},
"pot": {:hex, :pot, "1.0.2", "13abb849139fdc04ab8154986abbcb63bdee5de6ed2ba7e1713527e33df923dd", [:rebar3], [], "hexpm", "78fe127f5a4f5f919d6ea5a2a671827bd53eb9d37e5b4128c0ad3df99856c2e0"},
"quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "d736bfa7444112eb840027bb887832a0e403a4a3437f48028c3b29a2dbbd2543"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},

View file

@ -0,0 +1,29 @@
defmodule Pleroma.Repo.Migrations.AddUserFrontendProfiles do
use Ecto.Migration
def up do
create_if_not_exists table("user_frontend_setting_profiles", primary_key: false) do
add(:user_id, references(:users, type: :uuid, on_delete: :delete_all), primary_key: true)
add(:frontend_name, :string, primary_key: true)
add(:profile_name, :string, primary_key: true)
add(:version, :integer)
add(:settings, :map)
timestamps()
end
create_if_not_exists(index(:user_frontend_setting_profiles, [:user_id, :frontend_name]))
create_if_not_exists(
unique_index(:user_frontend_setting_profiles, [:user_id, :frontend_name, :profile_name])
)
end
def down do
drop_if_exists(table("user_frontend_setting_profiles"))
drop_if_exists(index(:user_frontend_setting_profiles, [:user_id, :frontend_name]))
drop_if_exists(
unique_index(:user_frontend_setting_profiles, [:user_id, :frontend_name, :profile_name])
)
end
end

View file

@ -2,28 +2,24 @@
# XXX: This should be removed when elixir's releases get custom command support
detect_flavour() {
arch="$(uname -m)"
if [ "$arch" = "x86_64" ]; then
arch="amd64"
elif [ "$arch" = "aarch64" ]; then
arch="arm64"
else
echo "Unsupported arch: $arch" >&2
exit 1
fi
arch="amd64"
# Special cases
if grep -qe "VERSION_CODENAME=jammy" /etc/os-release; then
echo "$arch-ubuntu-jammy"
else
if getconf GNU_LIBC_VERSION >/dev/null; then
libc_postfix=""
elif [ "$(ldd 2>&1 | head -c 9)" = "musl libc" ]; then
libc_postfix="-musl"
elif [ "$(find /lib/libc.musl* | wc -l)" ]; then
libc_postfix="-musl"
else
echo "Unsupported libc" >&2
exit 1
fi
if getconf GNU_LIBC_VERSION >/dev/null; then
libc_postfix=""
elif [ "$(ldd 2>&1 | head -c 9)" = "musl libc" ]; then
libc_postfix="-musl"
elif [ "$(find /lib/libc.musl* | wc -l)" ]; then
libc_postfix="-musl"
else
echo "Unsupported libc" >&2
exit 1
fi
echo "$arch$libc_postfix"
echo "$arch$libc_postfix"
fi
}
detect_branch() {

View file

@ -0,0 +1,196 @@
defmodule Pleroma.Akkoma.FrontendSettingsProfileTest do
use Pleroma.DataCase, async: true
use Oban.Testing, repo: Pleroma.Repo
alias Pleroma.Akkoma.FrontendSettingsProfile
import Pleroma.Factory
describe "changeset/2" do
test "valid" do
user = insert(:user)
frontend_name = "test"
profile_name = "test"
settings = %{"test" => "test"}
struct = %FrontendSettingsProfile{}
attrs = %{
user_id: user.id,
frontend_name: frontend_name,
profile_name: profile_name,
settings: settings,
version: 1
}
assert %{valid?: true} = FrontendSettingsProfile.changeset(struct, attrs)
end
test "when settings is too long" do
clear_config([:instance, :max_frontend_settings_json_chars], 10)
user = insert(:user)
frontend_name = "test"
profile_name = "test"
settings = %{"verylong" => "verylongoops"}
struct = %FrontendSettingsProfile{}
attrs = %{
user_id: user.id,
frontend_name: frontend_name,
profile_name: profile_name,
settings: settings,
version: 1
}
assert %{valid?: false, errors: [settings: {"is too long", _}]} =
FrontendSettingsProfile.changeset(struct, attrs)
end
test "when frontend name is too short" do
user = insert(:user)
frontend_name = ""
profile_name = "test"
settings = %{"test" => "test"}
struct = %FrontendSettingsProfile{}
attrs = %{
user_id: user.id,
frontend_name: frontend_name,
profile_name: profile_name,
settings: settings,
version: 1
}
assert %{valid?: false, errors: [frontend_name: {"can't be blank", _}]} =
FrontendSettingsProfile.changeset(struct, attrs)
end
test "when profile name is too short" do
user = insert(:user)
frontend_name = "test"
profile_name = ""
settings = %{"test" => "test"}
struct = %FrontendSettingsProfile{}
attrs = %{
user_id: user.id,
frontend_name: frontend_name,
profile_name: profile_name,
settings: settings,
version: 1
}
assert %{valid?: false, errors: [profile_name: {"can't be blank", _}]} =
FrontendSettingsProfile.changeset(struct, attrs)
end
test "when version is negative" do
user = insert(:user)
frontend_name = "test"
profile_name = "test"
settings = %{"test" => "test"}
struct = %FrontendSettingsProfile{}
attrs = %{
user_id: user.id,
frontend_name: frontend_name,
profile_name: profile_name,
settings: settings,
version: -1
}
assert %{valid?: false, errors: [version: {"must be greater than %{number}", _}]} =
FrontendSettingsProfile.changeset(struct, attrs)
end
end
describe "create_or_update/2" do
test "it should create a new record" do
user = insert(:user)
frontend_name = "test"
profile_name = "test"
settings = %{"test" => "test"}
assert {:ok, %FrontendSettingsProfile{}} =
FrontendSettingsProfile.create_or_update(
user,
frontend_name,
profile_name,
settings,
1
)
end
test "it should update a record" do
user = insert(:user)
frontend_name = "test"
profile_name = "test"
insert(:frontend_setting_profile,
user: user,
frontend_name: frontend_name,
profile_name: profile_name,
settings: %{"test" => "test"},
version: 1
)
settings = %{"test" => "test2"}
assert {:ok, %FrontendSettingsProfile{settings: ^settings}} =
FrontendSettingsProfile.create_or_update(
user,
frontend_name,
profile_name,
settings,
2
)
end
end
describe "get_all_by_user_and_frontend_name/2" do
test "it should return all records" do
user = insert(:user)
frontend_name = "test"
insert(:frontend_setting_profile,
user: user,
frontend_name: frontend_name,
profile_name: "profileA",
settings: %{"test" => "test"},
version: 1
)
insert(:frontend_setting_profile,
user: user,
frontend_name: frontend_name,
profile_name: "profileB",
settings: %{"test" => "test"},
version: 1
)
assert [%FrontendSettingsProfile{profile_name: "profileA"}, %{profile_name: "profileB"}] =
FrontendSettingsProfile.get_all_by_user_and_frontend_name(user, frontend_name)
end
end
describe "get_by_user_and_frontend_name_and_profile_name/3" do
test "it should return a record" do
user = insert(:user)
frontend_name = "test"
profile_name = "profileA"
insert(:frontend_setting_profile,
user: user,
frontend_name: frontend_name,
profile_name: profile_name,
settings: %{"test" => "test"},
version: 1
)
assert %FrontendSettingsProfile{profile_name: "profileA"} =
FrontendSettingsProfile.get_by_user_and_frontend_name_and_profile_name(
user,
frontend_name,
profile_name
)
end
end
end

View file

@ -1149,18 +1149,6 @@ test "it doesn't return notifications for domain-blocked non-followed user", %{u
assert Notification.for_user(user) == []
end
test "it returns notifications for domain-blocked but followed user" do
user = insert(:user)
blocked = insert(:user, ap_id: "http://some-domain.com")
{:ok, user} = User.block_domain(user, "some-domain.com")
{:ok, _, _} = User.follow(user, blocked)
{:ok, _activity} = CommonAPI.post(blocked, %{status: "hey @#{user.nickname}"})
assert length(Notification.for_user(user)) == 1
end
test "it doesn't return notifications for muted thread", %{user: user} do
another_user = insert(:user)

View file

@ -47,7 +47,7 @@ test "indexes a local post on creation" do
Jason.decode!(body)
)
json(%{updateId: 1})
json(%{taskUid: 1})
end)
{:ok, activity} =
@ -100,11 +100,11 @@ test "deletes posts from index when deleted locally" do
Jason.decode!(body)
)
json(%{updateId: 1})
json(%{taskUid: 1})
%{method: :delete, url: "http://127.0.0.1:7700/indexes/objects/documents/" <> id} ->
assert String.length(id) > 1
json(%{updateId: 2})
json(%{taskUid: 2})
end)
{:ok, activity} =

View file

@ -311,7 +311,7 @@ test "local users do not automatically follow local locked accounts" do
describe "unfollow/2" do
setup do: clear_config([:instance, :external_user_synchronization])
test "unfollow with syncronizes external user" do
test "unfollow with synchronizes external user" do
clear_config([:instance, :external_user_synchronization], true)
followed =
@ -2260,7 +2260,7 @@ test "updates the counters normally on following/getting a follow when disabled"
assert other_user.follower_count == 1
end
test "syncronizes the counters with the remote instance for the followed when enabled" do
test "synchronizes the counters with the remote instance for the followed when enabled" do
clear_config([:instance, :external_user_synchronization], false)
user = insert(:user)
@ -2282,7 +2282,7 @@ test "syncronizes the counters with the remote instance for the followed when en
assert other_user.follower_count == 437
end
test "syncronizes the counters with the remote instance for the follower when enabled" do
test "synchronizes the counters with the remote instance for the follower when enabled" do
clear_config([:instance, :external_user_synchronization], false)
user = insert(:user)

View file

@ -559,6 +559,10 @@ test "it inserts an incoming activity into the database", %{conn: conn} do
conn =
conn
|> assign(:valid_signature, true)
|> put_req_header(
"signature",
"keyId=\"http://mastodon.example.org/users/admin/main-key\""
)
|> put_req_header("content-type", "application/activity+json")
|> post("/inbox", data)
@ -589,6 +593,7 @@ test "it inserts an incoming activity into the database" <>
conn =
conn
|> assign(:valid_signature, true)
|> put_req_header("signature", "keyId=\"#{user.ap_id}/main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/inbox", data)
@ -602,12 +607,15 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn} do
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
sender_url = data["actor"]
sender = insert(:user, ap_id: data["actor"])
Instances.set_consistently_unreachable(sender_url)
refute Instances.reachable?(sender_url)
conn =
conn
|> assign(:valid_signature, true)
|> put_req_header("signature", "keyId=\"#{sender.ap_id}/main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/inbox", data)
@ -632,6 +640,7 @@ test "accept follow activity", %{conn: conn} do
assert "ok" ==
conn
|> assign(:valid_signature, true)
|> put_req_header("signature", "keyId=\"#{followed_relay.ap_id}/main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/inbox", accept)
|> json_response(200)
@ -698,6 +707,11 @@ test "accepts Add/Remove activities", %{conn: conn} do
actor = "https://example.com/users/lain"
insert(:user,
ap_id: actor,
featured_address: "https://example.com/users/lain/collections/featured"
)
Tesla.Mock.mock(fn
%{
method: :get,
@ -743,6 +757,7 @@ test "accepts Add/Remove activities", %{conn: conn} do
assert "ok" ==
conn
|> assign(:valid_signature, true)
|> put_req_header("signature", "keyId=\"#{actor}/main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/inbox", data)
|> json_response(200)
@ -750,6 +765,7 @@ test "accepts Add/Remove activities", %{conn: conn} do
ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
assert Activity.get_by_ap_id(data["id"])
user = User.get_cached_by_ap_id(data["actor"])
assert user.pinned_objects[data["object"]]
data = %{
@ -764,6 +780,7 @@ test "accepts Add/Remove activities", %{conn: conn} do
assert "ok" ==
conn
|> assign(:valid_signature, true)
|> put_req_header("signature", "keyId=\"#{actor}/main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/inbox", data)
|> json_response(200)
@ -790,6 +807,12 @@ test "mastodon pin/unpin", %{conn: conn} do
actor = "https://example.com/users/lain"
sender =
insert(:user,
ap_id: actor,
featured_address: "https://example.com/users/lain/collections/featured"
)
Tesla.Mock.mock(fn
%{
method: :get,
@ -844,6 +867,7 @@ test "mastodon pin/unpin", %{conn: conn} do
assert "ok" ==
conn
|> assign(:valid_signature, true)
|> put_req_header("signature", "keyId=\"#{sender.ap_id}/main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/inbox", data)
|> json_response(200)
@ -863,6 +887,7 @@ test "mastodon pin/unpin", %{conn: conn} do
assert "ok" ==
conn
|> assign(:valid_signature, true)
|> put_req_header("signature", "keyId=\"#{actor}/main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/inbox", data)
|> json_response(200)
@ -894,6 +919,7 @@ test "it inserts an incoming activity into the database", %{conn: conn, data: da
conn =
conn
|> assign(:valid_signature, true)
|> put_req_header("signature", "keyId=\"#{data["actor"]}/main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data)
@ -915,6 +941,7 @@ test "it accepts messages with to as string instead of array", %{conn: conn, dat
conn =
conn
|> assign(:valid_signature, true)
|> put_req_header("signature", "keyId=\"#{data["actor"]}/main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data)
@ -936,6 +963,7 @@ test "it accepts messages with cc as string instead of array", %{conn: conn, dat
conn =
conn
|> assign(:valid_signature, true)
|> put_req_header("signature", "keyId=\"#{data["actor"]}/main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data)
@ -960,6 +988,7 @@ test "it accepts messages with bcc as string instead of array", %{conn: conn, da
conn =
conn
|> assign(:valid_signature, true)
|> put_req_header("signature", "keyId=\"#{data["actor"]}/main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data)
@ -987,6 +1016,7 @@ test "it accepts announces with to as string instead of array", %{conn: conn} do
conn =
conn
|> assign(:valid_signature, true)
|> put_req_header("signature", "keyId=\"#{announcer.ap_id}/main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data)
@ -1017,6 +1047,7 @@ test "it accepts messages from actors that are followed by the user", %{
conn =
conn
|> assign(:valid_signature, true)
|> put_req_header("signature", "keyId=\"#{actor.ap_id}/main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{recipient.nickname}/inbox", data)
@ -1063,6 +1094,7 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn, da
conn =
conn
|> assign(:valid_signature, true)
|> put_req_header("signature", "keyId=\"#{data["actor"]}/main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data)
@ -1101,6 +1133,7 @@ test "it removes all follower collections but actor's", %{conn: conn} do
conn
|> assign(:valid_signature, true)
|> put_req_header("signature", "keyId=\"#{actor.ap_id}/main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{recipient.nickname}/inbox", data)
|> json_response(200)
@ -1193,6 +1226,7 @@ test "forwarded report", %{conn: conn} do
conn
|> assign(:valid_signature, true)
|> put_req_header("signature", "keyId=\"#{actor.ap_id}/main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{reported_user.nickname}/inbox", data)
|> json_response(200)
@ -1248,6 +1282,7 @@ test "forwarded report from mastodon", %{conn: conn} do
conn
|> assign(:valid_signature, true)
|> put_req_header("signature", "keyId=\"#{remote_actor}/main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{reported_user.nickname}/inbox", data)
|> json_response(200)

View file

@ -1632,7 +1632,7 @@ test "fetches only public posts for other users" do
end
describe "fetch_follow_information_for_user" do
test "syncronizes following/followers counters" do
test "synchronizes following/followers counters" do
user =
insert(:user,
local: false,

View file

@ -0,0 +1,122 @@
defmodule Pleroma.Web.AkkomaAPI.FrontendSettingsControllerTest do
use Pleroma.Web.ConnCase, async: true
import Pleroma.Factory
alias Pleroma.Akkoma.FrontendSettingsProfile
describe "GET /api/v1/akkoma/frontend_settings/:frontend_name" do
test "it returns a list of profiles" do
%{conn: conn, user: user} = oauth_access(["read"])
insert(:frontend_setting_profile, user: user, frontend_name: "test", profile_name: "test1")
insert(:frontend_setting_profile, user: user, frontend_name: "test", profile_name: "test2")
response =
conn
|> get("/api/v1/akkoma/frontend_settings/test")
|> json_response_and_validate_schema(200)
assert response == [
%{"name" => "test1", "version" => 1},
%{"name" => "test2", "version" => 1}
]
end
end
describe "GET /api/v1/akkoma/frontend_settings/:frontend_name/:profile_name" do
test "it returns 404 if not found" do
%{conn: conn} = oauth_access(["read"])
conn
|> get("/api/v1/akkoma/frontend_settings/unknown_frontend/unknown_profile")
|> json_response_and_validate_schema(404)
end
test "it returns 200 if found" do
%{conn: conn, user: user} = oauth_access(["read"])
insert(:frontend_setting_profile,
user: user,
frontend_name: "test",
profile_name: "test1",
settings: %{"test" => "test"}
)
response =
conn
|> get("/api/v1/akkoma/frontend_settings/test/test1")
|> json_response_and_validate_schema(200)
assert response == %{"settings" => %{"test" => "test"}, "version" => 1}
end
end
describe "PUT /api/v1/akkoma/frontend_settings/:frontend_name/:profile_name" do
test "puts a config" do
%{conn: conn, user: user} = oauth_access(["write"])
settings = %{"test" => "test2"}
response =
conn
|> put_req_header("content-type", "application/json")
|> put("/api/v1/akkoma/frontend_settings/test/test1", %{
"settings" => settings,
"version" => 1
})
|> json_response_and_validate_schema(200)
assert response == settings
assert %FrontendSettingsProfile{settings: ^settings} =
FrontendSettingsProfile.get_by_user_and_frontend_name_and_profile_name(
user,
"test",
"test1"
)
end
test "refuses to overwrite a newer config" do
%{conn: conn, user: user} = oauth_access(["write"])
insert(:frontend_setting_profile,
user: user,
frontend_name: "test",
profile_name: "test1",
settings: %{"test" => "test"},
version: 2
)
conn
|> put_req_header("content-type", "application/json")
|> put("/api/v1/akkoma/frontend_settings/test/test1", %{
"settings" => %{"test" => "test2"},
"version" => 1
})
|> json_response_and_validate_schema(422)
end
end
describe "DELETE /api/v1/akkoma/frontend_settings/:frontend_name/:profile_name" do
test "deletes a config" do
%{conn: conn, user: user} = oauth_access(["write"])
insert(:frontend_setting_profile,
user: user,
frontend_name: "test",
profile_name: "test1",
settings: %{"test" => "test"},
version: 2
)
conn
|> delete("/api/v1/akkoma/frontend_settings/test/test1")
|> json_response_and_validate_schema(200)
assert FrontendSettingsProfile.get_by_user_and_frontend_name_and_profile_name(
user,
"test",
"test1"
) == nil
end
end
end

View file

@ -1921,4 +1921,48 @@ test "create a note on a user" do
|> get("/api/v1/accounts/relationships?id=#{other_user.id}")
|> json_response_and_validate_schema(200)
end
describe "remove from followers" do
setup do: oauth_access(["follow"])
test "removing user from followers", %{conn: conn, user: user} do
%{id: other_user_id} = other_user = insert(:user)
CommonAPI.follow(other_user, user)
assert %{"id" => ^other_user_id, "followed_by" => false} =
conn
|> post("/api/v1/accounts/#{other_user_id}/remove_from_followers")
|> json_response_and_validate_schema(200)
refute User.following?(other_user, user)
end
test "removing remote user from followers", %{conn: conn, user: user} do
%{id: other_user_id} = other_user = insert(:user, local: false)
CommonAPI.follow(other_user, user)
assert User.following?(other_user, user)
assert %{"id" => ^other_user_id, "followed_by" => false} =
conn
|> post("/api/v1/accounts/#{other_user_id}/remove_from_followers")
|> json_response_and_validate_schema(200)
refute User.following?(other_user, user)
end
test "removing user from followers errors", %{user: user, conn: conn} do
# self remove
conn_res = post(conn, "/api/v1/accounts/#{user.id}/remove_from_followers")
assert %{"error" => "Can not unfollow yourself"} =
json_response_and_validate_schema(conn_res, 400)
# remove non existing user
conn_res = post(conn, "/api/v1/accounts/doesntexist/remove_from_followers")
assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn_res, 404)
end
end
end

View file

@ -11,7 +11,7 @@ defmodule Pleroma.Web.PleromaAPI.BackupControllerTest do
setup do
clear_config([Pleroma.Upload, :uploader])
clear_config([Backup, :limit_days])
oauth_access(["read:accounts"])
oauth_access(["read:backups"])
end
test "GET /api/v1/pleroma/backups", %{user: user, conn: conn} do
@ -85,7 +85,7 @@ test "POST /api/v1/pleroma/backups", %{user: _user, conn: conn} do
test "Backup without email address" do
user = Pleroma.Factory.insert(:user, email: nil)
%{conn: conn} = oauth_access(["read:accounts"], user: user)
%{conn: conn} = oauth_access(["read:backups"], user: user)
assert is_nil(user.email)

View file

@ -9,6 +9,8 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlugTest do
import Tesla.Mock
import Plug.Conn
import Pleroma.Tests.Helpers, only: [clear_config: 2]
setup do
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
:ok
@ -47,6 +49,26 @@ test "it considers a mapped identity to be invalid when it mismatches a payload"
assert %{valid_signature: false} == conn.assigns
end
test "it considers a mapped identity to be invalid when the associated instance is blocked" do
clear_config([:activitypub, :authorized_fetch_mode], true)
clear_config([:mrf_simple, :reject], [
{"mastodon.example.org", "anime is banned"}
])
on_exit(fn ->
Pleroma.Config.put([:activitypub, :authorized_fetch_mode], false)
Pleroma.Config.put([:mrf_simple, :reject], [])
end)
conn =
build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
|> set_signature("http://mastodon.example.org/users/admin")
|> MappedSignatureToIdentityPlug.call(%{})
assert %{valid_signature: false} == conn.assigns
end
@tag skip: "known breakage; the testsuite presently depends on it"
test "it considers a mapped identity to be invalid when the identity cannot be found" do
conn =

View file

@ -663,4 +663,15 @@ def announcement_factory(params \\ %{}) do
|> Map.merge(params)
|> Pleroma.Announcement.add_rendered_properties()
end
def frontend_setting_profile_factory(params \\ %{}) do
%Pleroma.Akkoma.FrontendSettingsProfile{
user: build(:user),
frontend_name: "akkoma-fe",
profile_name: "default",
settings: %{"test" => "test"},
version: 1
}
|> Map.merge(params)
end
end