Merge branch 'develop' into feature/gen-magic
authorMark Felder <feld@FreeBSD.org>
Thu, 10 Sep 2020 21:02:11 +0000 (16:02 -0500)
committerMark Felder <feld@FreeBSD.org>
Thu, 10 Sep 2020 21:05:22 +0000 (16:05 -0500)
18 files changed:
1  2 
.gitlab-ci.yml
CHANGELOG.md
config/config.exs
config/description.exs
docs/installation/alpine_linux_en.md
docs/installation/arch_linux_en.md
docs/installation/debian_based_en.md
docs/installation/debian_based_jp.md
docs/installation/gentoo_en.md
docs/installation/otp_en.md
lib/pleroma/application.ex
lib/pleroma/upload.ex
lib/pleroma/web/activity_pub/activity_pub_controller.ex
lib/pleroma/web/pleroma_api/controllers/account_controller.ex
mix.exs
mix.lock
test/upload_test.exs
test/web/activity_pub/activity_pub_controller_test.exs

diff --combined .gitlab-ci.yml
index 685106969d360c90a9894d99fff25056faf7c4c9,dc953a9291802571bbb021c7b43e5c72b9187bb1..29eb8d6b94020153924cd6014311777de7d3810a
@@@ -1,4 -1,4 +1,4 @@@
- image: elixir:1.8.1
+ image: elixir:1.9.4
  
  variables: &global_variables
    POSTGRES_DB: pleroma_test
@@@ -22,10 -22,9 +22,11 @@@ stages
    - docker
  
  before_script:
+   - apt-get update && apt-get install -y cmake
    - mix local.hex --force
    - mix local.rebar --force
 +  - apt-get -qq update
 +  - apt-get install -y libmagic-dev
  
  build:
    stage: build
@@@ -60,6 -59,7 +61,7 @@@ unit-testing
      alias: postgres
      command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
    script:
+     - apt-get update && apt-get install -y libimage-exiftool-perl
      - mix deps.get
      - mix ecto.create
      - mix ecto.migrate
@@@ -93,6 -93,7 +95,7 @@@ unit-testing-rum
      <<: *global_variables
      RUM_ENABLED: "true"
    script:
+     - apt-get update && apt-get install -y libimage-exiftool-perl
      - mix deps.get
      - mix ecto.create
      - mix ecto.migrate
@@@ -172,8 -173,7 +175,7 @@@ stop_review_app
  
  amd64:
    stage: release
-   # TODO: Replace with upstream image when 1.9.0 comes out
-   image: rinpatch/elixir:1.9.0-rc.0
+   image: elixir:1.10.3
    only: &release-only
    - stable@pleroma/pleroma
    - develop@pleroma/pleroma
    variables: &release-variables
      MIX_ENV: prod
    before_script: &before-release
+   - apt-get update && apt-get install -y cmake
    - echo "import Mix.Config" > config/prod.secret.exs
    - mix local.hex --force
    - mix local.rebar --force
@@@ -210,12 -211,11 +213,11 @@@ amd64-musl
    stage: release
    artifacts: *release-artifacts
    only: *release-only
-   # TODO: Replace with upstream image when 1.9.0 comes out
-   image: rinpatch/elixir:1.9.0-rc.0-alpine
+   image: elixir:1.10.3-alpine 
    cache: *release-cache
    variables: *release-variables
    before_script: &before-release-musl
-   - apk add git gcc g++ musl-dev make
+   - apk add git gcc g++ musl-dev make cmake
    - echo "import Mix.Config" > config/prod.secret.exs
    - mix local.hex --force
    - mix local.rebar --force
@@@ -227,8 -227,7 +229,7 @@@ arm
    only: *release-only
    tags:
      - arm32
-   # TODO: Replace with upstream image when 1.9.0 comes out
-   image: rinpatch/elixir:1.9.0-rc.0-arm
+   image: elixir:1.10.3
    cache: *release-cache
    variables: *release-variables
    before_script: *before-release
@@@ -240,8 -239,7 +241,7 @@@ arm-musl
    only: *release-only
    tags:
      - arm32
-   # TODO: Replace with upstream image when 1.9.0 comes out
-   image: rinpatch/elixir:1.9.0-rc.0-arm-alpine
+   image: elixir:1.10.3-alpine
    cache: *release-cache
    variables: *release-variables
    before_script: *before-release-musl
@@@ -253,8 -251,7 +253,7 @@@ arm64
    only: *release-only
    tags:
      - arm
-   # TODO: Replace with upstream image when 1.9.0 comes out
-   image: rinpatch/elixir:1.9.0-rc.0-arm64
+   image: elixir:1.10.3
    cache: *release-cache
    variables: *release-variables
    before_script: *before-release
@@@ -267,7 -264,7 +266,7 @@@ arm64-musl
    tags:
      - arm
    # TODO: Replace with upstream image when 1.9.0 comes out
-   image: rinpatch/elixir:1.9.0-rc.0-arm64-alpine
+   image: elixir:1.10.3-alpine
    cache: *release-cache
    variables: *release-variables
    before_script: *before-release-musl
@@@ -285,6 -282,8 +284,8 @@@ docker
      IMAGE_TAG_SLUG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
      IMAGE_TAG_LATEST: $CI_REGISTRY_IMAGE:latest
      IMAGE_TAG_LATEST_STABLE: $CI_REGISTRY_IMAGE:latest-stable
+     DOCKER_BUILDX_URL: https://github.com/docker/buildx/releases/download/v0.4.1/buildx-v0.4.1.linux-amd64
+     DOCKER_BUILDX_HASH: 71a7d01439aa8c165a25b59c44d3f016fddbd98b
    before_script: &before-docker
      - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
      - docker pull $IMAGE_TAG_SLUG || true
      - export CI_VCS_REF=$CI_COMMIT_SHORT_SHA
    allow_failure: true
    script:
-     - docker build --cache-from $IMAGE_TAG_SLUG --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $IMAGE_TAG -t $IMAGE_TAG_SLUG -t $IMAGE_TAG_LATEST .
-     - docker push $IMAGE_TAG
-     - docker push $IMAGE_TAG_SLUG
-     - docker push $IMAGE_TAG_LATEST
+     - mkdir -p /root/.docker/cli-plugins
+     - wget "${DOCKER_BUILDX_URL}" -O ~/.docker/cli-plugins/docker-buildx
+     - echo "${DOCKER_BUILDX_HASH}  /root/.docker/cli-plugins/docker-buildx" | sha1sum -c
+     - chmod +x ~/.docker/cli-plugins/docker-buildx
+     - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
+     - docker buildx create --name mbuilder --driver docker-container --use
+     - docker buildx inspect --bootstrap
+     - docker buildx build --platform linux/amd64,linux/arm/v7,linux/arm64/v8 --push --cache-from $IMAGE_TAG_SLUG --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $IMAGE_TAG -t $IMAGE_TAG_SLUG -t $IMAGE_TAG_LATEST .
    tags:
      - dind
    only:
@@@ -310,10 -313,14 +315,14 @@@ docker-stable
    before_script: *before-docker
    allow_failure: true
    script:
-     - docker build --cache-from $IMAGE_TAG_SLUG --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $IMAGE_TAG -t $IMAGE_TAG_SLUG -t $IMAGE_TAG_LATEST_STABLE .
-     - docker push $IMAGE_TAG
-     - docker push $IMAGE_TAG_SLUG
-     - docker push $IMAGE_TAG_LATEST_STABLE
+     - mkdir -p /root/.docker/cli-plugins
+     - wget "${DOCKER_BUILDX_URL}" -O ~/.docker/cli-plugins/docker-buildx
+     - echo "${DOCKER_BUILDX_HASH}  /root/.docker/cli-plugins/docker-buildx" | sha1sum -c
+     - chmod +x ~/.docker/cli-plugins/docker-buildx
+     - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
+     - docker buildx create --name mbuilder --driver docker-container --use
+     - docker buildx inspect --bootstrap
+     - docker buildx build --platform linux/amd64,linux/arm/v7,linux/arm64/v8 --push --cache-from $IMAGE_TAG_SLUG --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $IMAGE_TAG -t $IMAGE_TAG_SLUG -t $IMAGE_TAG_LATEST_STABLE .
    tags:
      - dind
    only:
@@@ -328,9 -335,15 +337,15 @@@ docker-release
    before_script: *before-docker
    allow_failure: true
    script:
-     - docker build --cache-from $IMAGE_TAG_SLUG --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $IMAGE_TAG -t       $IMAGE_TAG_SLUG .
-     - docker push $IMAGE_TAG
-     - docker push $IMAGE_TAG_SLUG
+   script:
+     - mkdir -p /root/.docker/cli-plugins
+     - wget "${DOCKER_BUILDX_URL}" -O ~/.docker/cli-plugins/docker-buildx
+     - echo "${DOCKER_BUILDX_HASH}  /root/.docker/cli-plugins/docker-buildx" | sha1sum -c
+     - chmod +x ~/.docker/cli-plugins/docker-buildx
+     - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
+     - docker buildx create --name mbuilder --driver docker-container --use
+     - docker buildx inspect --bootstrap
+     - docker buildx build --platform linux/amd64,linux/arm/v7,linux/arm64/v8 --push --cache-from $IMAGE_TAG_SLUG --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $IMAGE_TAG -t $IMAGE_TAG_SLUG .
    tags:
      - dind
    only:
diff --combined CHANGELOG.md
index 8925f31f63106419bb4c8442195b0967898598a9,75357f05edc20cf20cc62c471e1bc6e85fb687ac..a5c75bd4fa8931694d0fa30e74b0109270cbac85
@@@ -3,21 -3,104 +3,105 @@@ All notable changes to this project wil
  
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
  
- ## [unreleased]
+ ## Unreleased
  
  ### Changed
++- **Breaking** Requires `libmagic` (or `file`) to guess file types.
+ - Renamed `:await_up_timeout` in `:connections_pool` namespace to `:connect_timeout`, old name is deprecated.
+ - Renamed `:timeout` in `pools` namespace to `:recv_timeout`, old name is deprecated.
+ ### Removed
+ - **Breaking:** `Pleroma.Workers.Cron.StatsWorker` setting from Oban `:crontab` (moved to a simpler implementation).
+ - **Breaking:** `Pleroma.Workers.Cron.ClearOauthTokenWorker` setting from Oban `:crontab` (moved to scheduled jobs).
+ - **Breaking:** `Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker` setting from Oban `:crontab` (moved to scheduled jobs).
+ ### Changed
+ - Minimum lifetime for ephmeral activities changed to 10 minutes and made configurable (`:min_lifetime` option).
+ ## [2.1.1] - 2020-09-08
+ ### Security
+ - Fix possible DoS in Mastodon API user search due to an error in match clauses, leading to an infinite recursion and subsequent OOM with certain inputs.
+ - Fix metadata leak for accounts and statuses on private instances.
+ - Fix possible DoS in Admin API search using an atom leak vulnerability. Authentication with admin rights was required to exploit.
+ ### Changed
+ - **Breaking:** The metadata providers RelMe and Feed are no longer configurable. RelMe should always be activated and Feed only provides a <link> header tag for the actual RSS/Atom feed when the instance is public.
+ - Improved error message when cmake is not available at build stage.
+ ### Added
+ - Rich media failure tracking (along with `:failure_backoff` option).
+ ### Fixed
+ - Default HTTP adapter not respecting pool setting, leading to possible OOM.
+ - Fixed uploading webp images when the Exiftool Upload Filter is enabled by skipping them
+ - Mastodon API: Search parameter `following` now correctly returns the followings rather than the followers
+ - Mastodon API: Timelines hanging for (`number of posts with links * rich media timeout`) in the worst case.
+   Reduced to just rich media timeout.
+ - Mastodon API: Cards being wrong for preview statuses due to cache key collision.
+ - Password resets no longer processed for deactivated accounts.
+ - Favicon scraper raising exceptions on URLs longer than 255 characters.
+ ## [2.1.0] - 2020-08-28
+ ### Changed
+ - **Breaking:** The default descriptions on uploads are now empty. The old behavior (filename as default) can be configured, see the cheat sheet.
+ - **Breaking:** Added the ObjectAgePolicy to the default set of MRFs. This will delist and strip the follower collection of any message received that is older than 7 days. This will stop users from seeing very old messages in the timelines. The messages can still be viewed on the user's page and in conversations. They also still trigger notifications.
+ - **Breaking:** Elixir >=1.9 is now required (was >= 1.8)
+ - **Breaking:** Configuration: `:auto_linker, :opts` moved to `:pleroma, Pleroma.Formatter`. Old config namespace is deprecated.
+ - **Breaking:** Configuration: `:instance, welcome_user_nickname` moved to `:welcome, :direct_message, :sender_nickname`, `:instance, :welcome_message` moved to `:welcome, :direct_message, :message`. Old config namespace is deprecated.
+ - **Breaking:** LDAP: Fallback to local database authentication has been removed for security reasons and lack of a mechanism to ensure the passwords are synchronized when LDAP passwords are updated.
+ - **Breaking** Changed defaults for `:restrict_unauthenticated` so that when `:instance, :public` is set to `false` then all `:restrict_unauthenticated` items be effectively set to `true`. If you'd like to allow unauthenticated access to specific API endpoints on a private instance, please explicitly set `:restrict_unauthenticated` to non-default value in `config/prod.secret.exs`.
+ - In Conversations, return only direct messages as `last_status`
+ - Using the `only_media` filter on timelines will now exclude reblog media
  - MFR policy to set global expiration for all local Create activities
  - OGP rich media parser merged with TwitterCard
- - **Breaking** Requires `libmagic` (or `file`) to guess file types.
+ - Configuration: `:instance, rewrite_policy` moved to `:mrf, policies`, `:instance, :mrf_transparency` moved to `:mrf, :transparency`, `:instance, :mrf_transparency_exclusions` moved to `:mrf, :transparency_exclusions`. Old config namespace is deprecated.
+ - Configuration: `:media_proxy, whitelist` format changed to host with scheme (e.g. `http://example.com` instead of `example.com`). Domain format is deprecated.
  <details>
    <summary>API Changes</summary>
+ - **Breaking:** Pleroma API: The routes to update avatar, banner and background have been removed.
+ - **Breaking:** Image description length is limited now.
  - **Breaking:** Emoji API: changed methods and renamed routes.
+ - **Breaking:** Notification Settings API for suppressing notifications has been simplified down to `block_from_strangers`.
+ - **Breaking:** Notification Settings API option for hiding push notification contents has been renamed to `hide_notification_contents`.
+ - MastodonAPI: Allow removal of avatar, banner and background.
+ - Streaming: Repeats of a user's posts will no longer be pushed to the user's stream.
+ - Mastodon API: Added `pleroma.metadata.fields_limits` to /api/v1/instance
+ - Mastodon API: On deletion, returns the original post text.
+ - Mastodon API: Add `pleroma.unread_count` to the Marker entity.
+ - Mastodon API: Added `pleroma.metadata.post_formats` to /api/v1/instance
+ - Mastodon API (legacy): Allow query parameters for `/api/v1/domain_blocks`, e.g. `/api/v1/domain_blocks?domain=badposters.zone`
+ - Mastodon API: Make notifications about statuses from muted users and threads read automatically
+ - Pleroma API: `/api/pleroma/captcha` responses now include `seconds_valid` with an integer value.
+ </details>
+ <details>
+   <summary>Admin API Changes</summary>
+ - **Breaking** Changed relay `/api/pleroma/admin/relay` endpoints response format.
+ - Status visibility stats: now can return stats per instance.
+ - Mix task to refresh counter cache (`mix pleroma.refresh_counter_cache`)
  </details>
  
  ### Removed
  - **Breaking:** removed `with_move` parameter from notifications timeline.
  
  ### Added
+ - Frontends: Add mix task to install frontends.
+ - Frontends: Add configurable frontends for primary and admin fe.
+ - Configuration: Added a blacklist for email servers.
+ - Chats: Added `accepts_chat_messages` field to user, exposed in APIs and federation.
  - Chats: Added support for federated chats. For details, see the docs.
  - ActivityPub: Added support for existing AP ids for instances migrated from Mastodon.
  - Instance: Add `background_image` to configuration and `/api/v1/instance`
  - Notifications: Added `follow_request` notification type.
  - Added `:reject_deletes` group to SimplePolicy
  - MRF (`EmojiStealPolicy`): New MRF Policy which allows to automatically download emojis from remote instances
+ - Support pagination in emoji packs API (for packs and for files in pack)
+ - Support for viewing instances favicons next to posts and accounts
+ - Added Pleroma.Upload.Filter.Exiftool as an alternate EXIF stripping mechanism targeting GPS/location metadata.
+ - "By approval" registrations mode.
+ - Configuration: Added `:welcome` settings for the welcome message to newly registered users. You can send a welcome message as a direct message, chat or email.
+ - Ability to hide favourites and emoji reactions in the API with `[:instance, :show_reactions]` config.
  <details>
    <summary>API Changes</summary>
+ - Mastodon API: Add pleroma.parent_visible field to statuses.
  - Mastodon API: Extended `/api/v1/instance`.
  - Mastodon API: Support for `include_types` in `/api/v1/notifications`.
  - Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
- - Mastodon API: Add support for filtering replies in public and home timelines
+ - Mastodon API: Add support for filtering replies in public and home timelines.
+ - Mastodon API: Support for `bot` field in `/api/v1/accounts/update_credentials`.
+ - Mastodon API: Support irreversible property for filters.
+ - Mastodon API: Add pleroma.favicon field to accounts.
  - Admin API: endpoints for create/update/delete OAuth Apps.
  - Admin API: endpoint for status view.
  - OTP: Add command to reload emoji packs
  </details>
  
  ### Fixed
+ - Fix list pagination and other list issues.
  - Support pagination in conversations API
  - **Breaking**: SimplePolicy `:reject` and `:accept` allow deletions again
  - Fix follower/blocks import when nicknames starts with @
  - Resolving Peertube accounts with Webfinger
  - `blob:` urls not being allowed by connect-src CSP
  - Mastodon API: fix `GET /api/v1/notifications` not returning the full result set
+ - Rich Media Previews for Twitter links
+ - Admin API: fix `GET /api/pleroma/admin/users/:nickname/credentials` returning 404 when getting the credentials of a remote user while `:instance, :limit_to_local_content` is set to `:unauthenticated`
+ - Fix CSP policy generation to include remote Captcha services
+ - Fix edge case where MediaProxy truncates media, usually caused when Caddy is serving content for the other Federated instance.
+ - Emoji Packs could not be listed when instance was set to `public: false`
+ - Fix whole_word always returning false on filter get requests
+ - Migrations not working on OTP releases if the database was connected over ssl
+ - Fix relay following
+ ## [2.0.7] - 2020-06-13
+ ### Security
+ - Fix potential DoSes exploiting atom leaks in rich media parser and the `UserAllowListPolicy` MRF policy
+ ### Fixed
+ - CSP: not allowing images/media from every host when mediaproxy is disabled
+ - CSP: not adding mediaproxy base url to image/media hosts
+ - StaticFE missing the CSS file
+ ### Upgrade notes
+ 1. Restart Pleroma
+ ## [2.0.6] - 2020-06-09
+ ### Security
+ - CSP: harden `image-src` and `media-src` when MediaProxy is used
+ ### Fixed
+ - AP C2S: Fix pagination in inbox/outbox
+ - Various compilation errors on OTP 23
+ - Mastodon API streaming: Repeats from muted threads not being filtered
+ ### Changed
+ - Various database performance improvements
+ ### Upgrade notes
+ 1. Run database migrations (inside Pleroma directory):
+   - OTP: `./bin/pleroma_ctl migrate`
+   - From Source: `mix ecto.migrate`
+ 2. Restart Pleroma
+ ## [2.0.5] - 2020-05-13
  
- ## [Unreleased (patch)]
+ ### Security
+ - Fix possible private status leaks in Mastodon Streaming API
+ ### Fixed
+ - Crashes when trying to block a user if block federation is disabled
+ - Not being able to start the instance without `erlang-eldap` installed
+ - Users with bios over the limit getting rejected
+ - Follower counters not being updated on incoming follow accepts
+ ### Upgrade notes
+ 1. Restart Pleroma
+ ## [2.0.4] - 2020-05-10
+ ### Security
+ - AP C2S: Fix a potential DoS by creating nonsensical objects that break timelines
  
  ### Fixed
+ - Peertube user lookups not working
+ - `InsertSkeletonsForDeletedUsers` migration failing on some instances
  - Healthcheck reporting the number of memory currently used, rather than allocated in total
- - `InsertSkeletonsForDeletedUsers` failing on some instances
+ - LDAP not being usable in OTP releases
+ - Default apache configuration having tls chain issues
+ ### Upgrade notes
+ #### Apache only
+ 1. Remove the following line from your config:
+ ```
+     SSLCertificateFile      /etc/letsencrypt/live/${servername}/cert.pem
+ ```
+ #### Everyone
+ 1. Restart Pleroma
  
  ## [2.0.3] - 2020-05-02
  
  2. Run database migrations (inside Pleroma directory):
    - OTP: `./bin/pleroma_ctl migrate`
    - From Source: `mix ecto.migrate`
+ 3. Reset status visibility counters (inside Pleroma directory):
+   - OTP: `./bin/pleroma_ctl refresh_counter_cache`
+   - From Source: `mix pleroma.refresh_counter_cache`
  
  
  ## [2.0.2] - 2020-04-08
  - Static-FE: Fix remote posts not being sanitized
  
  ### Fixed
+ =======
+ - Rate limiter crashes when there is no explicitly specified ip in the config
  - 500 errors when no `Accept` header is present if Static-FE is enabled
  - Instance panel not being updated immediately due to wrong `Cache-Control` headers
  - Statuses posted with BBCode/Markdown having unncessary newlines in Pleroma-FE
  - **Breaking**: Using third party engines for user recommendation
  <details>
    <summary>API Changes</summary>
  - **Breaking**: AdminAPI: migrate_from_db endpoint
  </details>
  
  - Mastodon API: `pleroma.thread_muted` to the Status entity
  - Mastodon API: Mark the direct conversation as read for the author when they send a new direct message
  - Mastodon API, streaming: Add `pleroma.direct_conversation_id` to the `conversation` stream event payload.
- - Mastodon API: Add `pleroma.unread_count` to the Marker entity
  - Admin API: Render whole status in grouped reports
  - Mastodon API: User timelines will now respect blocks, unless you are getting the user timeline of somebody you blocked (which would be empty otherwise).
  - Mastodon API: Favoriting / Repeating a post multiple times will now return the identical response every time. Before, executing that action twice would return an error ("already favorited") on the second try.
diff --combined config/config.exs
index f58b99faa4b31495c9cf3c8daf4156a78ca8b435,88c47fd032c382b9e92a021dcccdd4c5bbdba869..46a649b73a6e8a756df08eec8b608177f857ddee
@@@ -72,7 -72,8 +72,8 @@@ config :pleroma, Pleroma.Upload
        pool: :upload
      ]
    ],
-   filename_display_max_length: 30
+   filename_display_max_length: 30,
+   default_description: nil
  
  config :pleroma, Pleroma.Uploaders.Local, uploads: "uploads"
  
@@@ -97,6 -98,7 +98,7 @@@ config :pleroma, :uri_schemes
      "dat",
      "dweb",
      "gopher",
+     "hyper",
      "ipfs",
      "ipns",
      "irc",
@@@ -186,7 -188,9 +188,9 @@@ config :pleroma, :instance
    notify_email: "noreply@example.com",
    description: "Pleroma: An efficient and flexible fediverse server",
    background_image: "/images/city.jpg",
+   instance_thumbnail: "/instance/thumbnail.jpeg",
    limit: 5_000,
+   description_limit: 5_000,
    chat_limit: 5_000,
    remote_limit: 100_000,
    upload_limit: 16_000_000,
    registrations_open: true,
    invites_enabled: false,
    account_activation_required: false,
+   account_approval_required: false,
    federating: true,
    federation_incoming_replies_max_depth: 100,
    federation_reachability_timeout_days: 7,
      Pleroma.Web.ActivityPub.Publisher
    ],
    allow_relay: true,
-   rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy,
    public: true,
    quarantined_instances: [],
    managed_config: true,
      "text/markdown",
      "text/bbcode"
    ],
-   mrf_transparency: true,
-   mrf_transparency_exclusions: [],
    autofollowed_nicknames: [],
    max_pinned_statuses: 1,
    attachment_links: false,
-   welcome_user_nickname: nil,
-   welcome_message: nil,
    max_report_comment_size: 1000,
    safe_dm_mentions: false,
    healthcheck: false,
    max_remote_account_fields: 20,
    account_field_name_length: 512,
    account_field_value_length: 2048,
+   registration_reason_length: 500,
    external_user_synchronization: true,
    extended_nickname_format: true,
    cleanup_attachments: false,
        number: 5,
        length: 16
      ]
+   ],
+   show_reactions: true
+ config :pleroma, :welcome,
+   direct_message: [
+     enabled: false,
+     sender_nickname: nil,
+     message: nil
+   ],
+   chat_message: [
+     enabled: false,
+     sender_nickname: nil,
+     message: nil
+   ],
+   email: [
+     enabled: false,
+     sender: nil,
+     subject: "Welcome to <%= instance_name %>",
+     html: "Welcome to <%= instance_name %>",
+     text: "Welcome to <%= instance_name %>"
    ]
  
  config :pleroma, :feed,
@@@ -359,6 -380,7 +380,7 @@@ config :pleroma, :mrf_simple
    federated_timeline_removal: [],
    report_removal: [],
    reject: [],
+   followers_only: [],
    accept: [],
    avatar_removal: [],
    banner_removal: [],
@@@ -377,8 -399,9 +399,9 @@@ config :pleroma, :mrf_vocabulary
    accept: [],
    reject: []
  
+ # threshold of 7 days
  config :pleroma, :mrf_object_age,
-   threshold: 172_800,
+   threshold: 604_800,
    actions: [:delist, :strip_followers]
  
  config :pleroma, :rich_media,
      Pleroma.Web.RichMedia.Parsers.TwitterCard,
      Pleroma.Web.RichMedia.Parsers.OEmbed
    ],
+   failure_backoff: 60_000,
    ttl_setters: [Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl]
  
  config :pleroma, :media_proxy,
    ],
    whitelist: []
  
+ config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Http,
+   method: :purge,
+   headers: [],
+   options: []
+ config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Script, script_path: nil
  config :pleroma, :chat, enabled: true
  
  config :phoenix, :format_encoders, json: Jason
@@@ -423,12 -454,15 +454,15 @@@ config :pleroma, :gopher
  config :pleroma, Pleroma.Web.Metadata,
    providers: [
      Pleroma.Web.Metadata.Providers.OpenGraph,
-     Pleroma.Web.Metadata.Providers.TwitterCard,
-     Pleroma.Web.Metadata.Providers.RelMe,
-     Pleroma.Web.Metadata.Providers.Feed
+     Pleroma.Web.Metadata.Providers.TwitterCard
    ],
    unfurl_nsfw: false
  
+ config :pleroma, Pleroma.Web.Preload,
+   providers: [
+     Pleroma.Web.Preload.Providers.Instance
+   ]
  config :pleroma, :http_security,
    enabled: true,
    sts: false,
@@@ -481,15 -515,22 +515,22 @@@ config :pleroma, Pleroma.User
      "user-search",
      "user_exists",
      "users",
-     "web"
-   ]
+     "web",
+     "verify_credentials",
+     "update_credentials",
+     "relationships",
+     "search",
+     "confirmation_resend",
+     "mfa"
+   ],
+   email_blacklist: []
  
  config :pleroma, Oban,
    repo: Pleroma.Repo,
-   verbose: false,
-   prune: {:maxlen, 1500},
+   log: false,
    queues: [
      activity_expiration: 10,
+     token_expiration: 5,
      federator_incoming: 50,
      federator_outgoing: 50,
      web_push: 50,
      attachments_cleanup: 5,
      new_users_digest: 1
    ],
+   plugins: [Oban.Plugins.Pruner],
    crontab: [
-     {"0 0 * * *", Pleroma.Workers.Cron.ClearOauthTokenWorker},
-     {"0 * * * *", Pleroma.Workers.Cron.StatsWorker},
-     {"* * * * *", Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker},
      {"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
      {"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}
    ]
@@@ -515,16 -554,14 +554,14 @@@ config :pleroma, :workers
      federator_outgoing: 5
    ]
  
- config :auto_linker,
-   opts: [
-     extra: true,
-     # TODO: Set to :no_scheme when it works properly
-     validate_tld: true,
-     class: false,
-     strip_prefix: false,
-     new_window: false,
-     rel: "ugc"
-   ]
+ config :pleroma, Pleroma.Formatter,
+   class: false,
+   rel: "ugc",
+   new_window: false,
+   truncate: false,
+   strip_prefix: false,
+   extra: true,
+   validate_tld: :no_scheme
  
  config :pleroma, :ldap,
    enabled: System.get_env("LDAP_ENABLED") == "true",
@@@ -617,12 -654,65 +654,65 @@@ config :pleroma, :rate_limit
    account_confirmation_resend: {8_640_000, 5},
    ap_routes: {60_000, 15}
  
- config :pleroma, Pleroma.ActivityExpiration, enabled: true
+ config :pleroma, Pleroma.Workers.PurgeExpiredActivity, enabled: true, min_lifetime: 600
  
  config :pleroma, Pleroma.Plugs.RemoteIp, enabled: true
  
  config :pleroma, :static_fe, enabled: false
  
+ # Example of frontend configuration
+ # This example will make us serve the primary frontend from the
+ # frontends directory within your `:pleroma, :instance, static_dir`.
+ # e.g., instance/static/frontends/pleroma/develop/
+ #
+ # With no frontend configuration, the bundled files from the `static` directory will
+ # be used.
+ #
+ # config :pleroma, :frontends,
+ # primary: %{"name" => "pleroma-fe", "ref" => "develop"},
+ # admin: %{"name" => "admin-fe", "ref" => "stable"},
+ # available: %{...}
+ config :pleroma, :frontends,
+   available: %{
+     "kenoma" => %{
+       "name" => "kenoma",
+       "git" => "https://git.pleroma.social/lambadalambda/kenoma",
+       "build_url" =>
+         "https://git.pleroma.social/lambadalambda/kenoma/-/jobs/artifacts/${ref}/download?job=build",
+       "ref" => "master"
+     },
+     "pleroma-fe" => %{
+       "name" => "pleroma-fe",
+       "git" => "https://git.pleroma.social/pleroma/pleroma-fe",
+       "build_url" =>
+         "https://git.pleroma.social/pleroma/pleroma-fe/-/jobs/artifacts/${ref}/download?job=build",
+       "ref" => "develop"
+     },
+     "fedi-fe" => %{
+       "name" => "fedi-fe",
+       "git" => "https://git.pleroma.social/pleroma/fedi-fe",
+       "build_url" =>
+         "https://git.pleroma.social/pleroma/fedi-fe/-/jobs/artifacts/${ref}/download?job=build",
+       "ref" => "master"
+     },
+     "admin-fe" => %{
+       "name" => "admin-fe",
+       "git" => "https://git.pleroma.social/pleroma/admin-fe",
+       "build_url" =>
+         "https://git.pleroma.social/pleroma/admin-fe/-/jobs/artifacts/${ref}/download?job=build",
+       "ref" => "develop"
+     },
+     "soapbox-fe" => %{
+       "name" => "soapbox-fe",
+       "git" => "https://gitlab.com/soapbox-pub/soapbox-fe",
+       "build_url" =>
+         "https://gitlab.com/soapbox-pub/soapbox-fe/-/jobs/artifacts/${ref}/download?job=build-production",
+       "ref" => "v1.0.0",
+       "build_dir" => "static"
+     }
+   }
  config :pleroma, :web_cache_ttl,
    activity_pub: nil,
    activity_pub_question: 30_000
@@@ -636,32 -726,34 +726,34 @@@ config :pleroma, Pleroma.Repo
    prepare: :unnamed
  
  config :pleroma, :connections_pool,
-   checkin_timeout: 250,
+   reclaim_multiplier: 0.1,
+   connection_acquisition_wait: 250,
+   connection_acquisition_retries: 5,
    max_connections: 250,
-   retry: 1,
-   retry_timeout: 1000,
-   await_up_timeout: 5_000
+   max_idle_time: 30_000,
+   retry0,
+   connect_timeout: 5_000
  
  config :pleroma, :pools,
    federation: [
      size: 50,
-     max_overflow: 10,
-     timeout: 150_000
+     max_waiting: 10,
+     recv_timeout: 10_000
    ],
    media: [
      size: 50,
-     max_overflow: 10,
-     timeout: 150_000
+     max_waiting: 10,
+     recv_timeout: 10_000
    ],
    upload: [
      size: 25,
-     max_overflow: 5,
-     timeout: 300_000
+     max_waiting: 5,
+     recv_timeout: 15_000
    ],
    default: [
      size: 10,
-     max_overflow: 2,
-     timeout: 10_000
+     max_waiting: 2,
+     recv_timeout: 5_000
    ]
  
  config :pleroma, :hackney_pools,
      timeout: 300_000
    ]
  
 +config :pleroma, :majic_pool, size: 2
 +
+ private_instance? = :if_instance_is_private
  config :pleroma, :restrict_unauthenticated,
-   timelines: %{local: false, federated: false},
-   profiles: %{local: false, remote: false},
-   activities: %{local: false, remote: false}
+   timelines: %{local: private_instance?, federated: private_instance?},
+   profiles: %{local: private_instance?, remote: private_instance?},
+   activities: %{local: private_instance?, remote: private_instance?}
  
  config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: false
  
+ config :pleroma, :mrf,
+   policies: Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy,
+   transparency: true,
+   transparency_exclusions: []
+ config :tzdata, :http_client, Pleroma.HTTP.Tzdata
+ config :ex_aws, http_client: Pleroma.HTTP.ExAws
+ config :pleroma, :instances_favicons, enabled: false
+ config :floki, :html_parser, Floki.HTMLParser.FastHtml
+ config :pleroma, Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.PleromaAuthenticator
  # Import environment specific config. This must remain at the bottom
  # of this file so it overrides the configuration defined above.
  import_config "#{Mix.env()}.exs"
diff --combined config/description.exs
index 2afc5e3666909f0ce8f8e49b071033f5aad44d42,82c7bc6a7d0c98a45b43d8323d120ed490c1320e..d05adf88be5c218b3c745dfce3ca1ca78d156da3
@@@ -12,6 -12,55 +12,55 @@@ websocket_config = 
    compress: false
  ]
  
+ installed_frontend_options = [
+   %{
+     key: "name",
+     label: "Name",
+     type: :string,
+     description:
+       "Name of the installed frontend. Valid config must include both `Name` and `Reference` values."
+   },
+   %{
+     key: "ref",
+     label: "Reference",
+     type: :string,
+     description:
+       "Reference of the installed frontend to be used. Valid config must include both `Name` and `Reference` values."
+   }
+ ]
+ frontend_options = [
+   %{
+     key: "name",
+     label: "Name",
+     type: :string,
+     description: "Name of the frontend."
+   },
+   %{
+     key: "ref",
+     label: "Reference",
+     type: :string,
+     description: "Reference of the frontend to be used."
+   },
+   %{
+     key: "git",
+     type: :string,
+     description: "URL of the git repository of the frontend"
+   },
+   %{
+     key: "build_url",
+     type: :string,
+     description:
+       "Either an url to a zip file containing the frontend or a template to build it by inserting the `ref`. The string `${ref}` will be replaced by the configured `ref`.",
+     example: "https://some.url/builds/${ref}.zip"
+   },
+   %{
+     key: "build_dir",
+     type: :string,
+     description: "The directory inside the zip file "
+   }
+ ]
  config :pleroma, :config_description, [
    %{
      group: :pleroma,
          key: :uploader,
          type: :module,
          description: "Module which will be used for uploads",
-         suggestions: [Pleroma.Uploaders.Local, Pleroma.Uploaders.S3]
+         suggestions: {:list_behaviour_implementations, Pleroma.Uploaders.Uploader}
        },
        %{
          key: :filters,
          type: {:list, :module},
          description:
            "List of filter modules for uploads. Module names are shortened (removed leading `Pleroma.Upload.Filter.` part), but on adding custom module you need to use full name.",
-         suggestions:
-           Generator.list_modules_in_dir(
-             "lib/pleroma/upload/filter",
-             "Elixir.Pleroma.Upload.Filter."
-           )
+         suggestions: {:list_behaviour_implementations, Pleroma.Upload.Filter}
        },
        %{
          key: :link_name,
          type: :boolean,
          description:
-           "If enabled, a name parameter will be added to the url of the upload. For example `https://instance.tld/media/imagehash.png?name=realname.png`."
+           "If enabled, a name parameter will be added to the URL of the upload. For example `https://instance.tld/media/imagehash.png?name=realname.png`."
        },
        %{
          key: :base_url,
+         label: "Base URL",
          type: :string,
-         description: "Base url for the uploads, needed if you use CDN",
+         description: "Base URL for the uploads, needed if you use CDN",
          suggestions: [
            "https://cdn-host.com"
          ]
        },
        %{
          key: :proxy_opts,
+         label: "Proxy Options",
          type: :keyword,
          description: "Options for Pleroma.ReverseProxy",
          suggestions: [
            },
            %{
              key: :http,
+             label: "HTTP",
              type: :keyword,
              description: "HTTP options",
              children: [
        %{
          key: :args,
          type: [:string, {:list, :string}, {:list, :tuple}],
-         description: "List of actions for the mogrify command",
+         description:
+           "List of actions for the mogrify command. It's possible to add self-written settings as string. " <>
+             "For example `auto-orient, strip, {\"resize\", \"3840x1080>\"}` value will be parsed into valid list of the settings.",
          suggestions: [
            "strip",
            "auto-orient",
    %{
      group: :pleroma,
      key: :uri_schemes,
+     label: "URI Schemes",
      type: :group,
      description: "URI schemes related settings",
      children: [
            "dat",
            "dweb",
            "gopher",
+           "hyper",
            "ipfs",
            "ipns",
            "irc",
          key: :invites_enabled,
          type: :boolean,
          description:
-           "Enable user invitations for admins (depends on `registrations_open` being disabled)."
+           "Enable user invitations for admins (depends on `registrations_open` being disabled)"
        },
        %{
          key: :account_activation_required,
          type: :boolean,
-         description: "Require users to confirm their emails before signing in."
+         description: "Require users to confirm their emails before signing in"
+       },
+       %{
+         key: :account_approval_required,
+         type: :boolean,
+         description: "Require users to be manually approved by an admin before signing in"
        },
        %{
          key: :federating,
          type: :boolean,
-         description: "Enable federation with other instances."
+         description: "Enable federation with other instances"
        },
        %{
          key: :federation_incoming_replies_max_depth,
          label: "Fed. reachability timeout days",
          type: :integer,
          description:
-           "Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.",
+           "Timeout (in days) of each external federation target being unreachable prior to pausing federating to it",
          suggestions: [
            7
          ]
          type: :boolean,
          description: "Enable Pleroma's Relay, which makes it possible to follow a whole instance"
        },
-       %{
-         key: :rewrite_policy,
-         type: [:module, {:list, :module}],
-         description:
-           "A list of enabled MRF policies. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.",
-         suggestions:
-           Generator.list_modules_in_dir(
-             "lib/pleroma/web/activity_pub/mrf",
-             "Elixir.Pleroma.Web.ActivityPub.MRF."
-           )
-       },
        %{
          key: :public,
          type: :boolean,
          description:
-           "Makes the client API in authentificated mode-only except for user-profiles." <>
-             " Useful for disabling the Local Timeline and The Whole Known Network."
+           "Makes the client API in authenticated mode-only except for user-profiles." <>
+             " Useful for disabling the Local Timeline and The Whole Known Network. " <>
+             " Note: when setting to `false`, please also check `:restrict_unauthenticated` setting."
        },
        %{
          key: :quarantined_instances,
            "text/bbcode"
          ]
        },
-       %{
-         key: :mrf_transparency,
-         label: "MRF transparency",
-         type: :boolean,
-         description:
-           "Make the content of your Message Rewrite Facility settings public (via nodeinfo)"
-       },
-       %{
-         key: :mrf_transparency_exclusions,
-         label: "MRF transparency exclusions",
-         type: {:list, :string},
-         description:
-           "Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.",
-         suggestions: [
-           "exclusion.com"
-         ]
-       },
        %{
          key: :extended_nickname_format,
          type: :boolean,
          type: :boolean,
          description: "Enable to automatically add attachment link text to statuses"
        },
-       %{
-         key: :welcome_message,
-         type: :string,
-         description:
-           "A message that will be sent to a newly registered users as a direct message",
-         suggestions: [
-           "Hi, @username! Welcome on board!"
-         ]
-       },
-       %{
-         key: :welcome_user_nickname,
-         type: :string,
-         description: "The nickname of the local user that sends the welcome message",
-         suggestions: [
-           "lain"
-         ]
-       },
        %{
          key: :max_report_comment_size,
          type: :integer,
        },
        %{
          key: :safe_dm_mentions,
+         label: "Safe DM mentions",
          type: :boolean,
          description:
            "If enabled, only mentions at the beginning of a post will be used to address people in direct messages." <>
        %{
          key: :skip_thread_containment,
          type: :boolean,
-         description: "Skip filtering out broken threads. Default: enabled"
+         description: "Skip filtering out broken threads. Default: enabled."
        },
        %{
          key: :limit_to_local_content,
            2048
          ]
        },
+       %{
+         key: :registration_reason_length,
+         type: :integer,
+         description: "Maximum registration reason length. Default: 500.",
+         suggestions: [
+           500
+         ]
+       },
        %{
          key: :external_user_synchronization,
          type: :boolean,
          children: [
            %{
              key: :totp,
+             label: "TOTP settings",
              type: :keyword,
              description: "TOTP settings",
              suggestions: [digits: 6, period: 30],
                  type: :integer,
                  suggestions: [30],
                  description:
-                   "a period for which the TOTP code will be valid, in seconds. Defaults to 30 seconds."
+                   "A period for which the TOTP code will be valid, in seconds. Defaults to 30 seconds."
                }
              ]
            },
                  key: :number,
                  type: :integer,
                  suggestions: [5],
-                 description: "number of backup codes to generate."
+                 description: "Number of backup codes to generate."
                },
                %{
                  key: :length,
        },
        %{
          key: :instance_thumbnail,
-         type: :string,
+         type: {:string, :image},
          description:
-           "The instance thumbnail image. It will appear in [Pleroma Instances](http://distsn.org/pleroma-instances.html)",
+           "The instance thumbnail can be any image that represents your instance and is used by some apps or services when they display information about your instance.",
          suggestions: ["/instance/thumbnail.jpeg"]
+       },
+       %{
+         key: :show_reactions,
+         type: :boolean,
+         description: "Let favourites and emoji reactions be viewed through the API."
+       }
+     ]
+   },
+   %{
+     group: :pleroma,
+     key: :welcome,
+     type: :group,
+     description: "Welcome messages settings",
+     children: [
+       %{
+         key: :direct_message,
+         type: :keyword,
+         descpiption: "Direct message settings",
+         children: [
+           %{
+             key: :enabled,
+             type: :boolean,
+             description: "Enables sending a direct message to newly registered users"
+           },
+           %{
+             key: :message,
+             type: :string,
+             description: "A message that will be sent to newly registered users",
+             suggestions: [
+               "Hi, @username! Welcome on board!"
+             ]
+           },
+           %{
+             key: :sender_nickname,
+             type: :string,
+             description: "The nickname of the local user that sends a welcome message",
+             suggestions: [
+               "lain"
+             ]
+           }
+         ]
+       },
+       %{
+         key: :chat_message,
+         type: :keyword,
+         descpiption: "Chat message settings",
+         children: [
+           %{
+             key: :enabled,
+             type: :boolean,
+             description: "Enables sending a chat message to newly registered users"
+           },
+           %{
+             key: :message,
+             type: :string,
+             description:
+               "A message that will be sent to newly registered users as a chat message",
+             suggestions: [
+               "Hello, welcome on board!"
+             ]
+           },
+           %{
+             key: :sender_nickname,
+             type: :string,
+             description: "The nickname of the local user that sends a welcome chat message",
+             suggestions: [
+               "lain"
+             ]
+           }
+         ]
+       },
+       %{
+         key: :email,
+         type: :keyword,
+         descpiption: "Email message settings",
+         children: [
+           %{
+             key: :enabled,
+             type: :boolean,
+             description: "Enables sending an email to newly registered users"
+           },
+           %{
+             key: :sender,
+             type: [:string, :tuple],
+             description:
+               "Email address and/or nickname that will be used to send the welcome email.",
+             suggestions: [
+               {"Pleroma App", "welcome@pleroma.app"}
+             ]
+           },
+           %{
+             key: :subject,
+             type: :string,
+             description:
+               "Subject of the welcome email. EEX template with user and instance_name variables can be used.",
+             suggestions: ["Welcome to <%= instance_name%>"]
+           },
+           %{
+             key: :html,
+             type: :string,
+             description:
+               "HTML content of the welcome email. EEX template with user and instance_name variables can be used.",
+             suggestions: ["<h1>Hello <%= user.name%>. Welcome to <%= instance_name%></h1>"]
+           },
+           %{
+             key: :text,
+             type: :string,
+             description:
+               "Text content of the welcome email. EEX template with user and instance_name variables can be used.",
+             suggestions: ["Hello <%= user.name%>. \n Welcome to <%= instance_name%>\n"]
+           }
+         ]
        }
      ]
    },
      group: :logger,
      type: :group,
      key: :ex_syslogger,
+     label: "ExSyslogger",
      description: "ExSyslogger-related settings",
      children: [
        %{
        %{
          key: :format,
          type: :string,
-         description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\".",
+         description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\"",
          suggestions: ["$metadata[$level] $message"]
        },
        %{
      group: :logger,
      type: :group,
      key: :console,
+     label: "Console Logger",
      description: "Console logger settings",
      children: [
        %{
        %{
          key: :format,
          type: :string,
-         description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\".",
+         description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\"",
          suggestions: ["$metadata[$level] $message"]
        },
        %{
    %{
      group: :quack,
      type: :group,
+     label: "Quack Logger",
      description: "Quack-related settings",
      children: [
        %{
        },
        %{
          key: :webhook_url,
+         label: "Webhook URL",
          type: :string,
          description: "Configure the Slack incoming webhook",
          suggestions: ["https://hooks.slack.com/services/YOUR-KEY-HERE"]
            },
            %{
              key: :background,
-             type: :string,
+             type: {:string, :image},
              description:
                "URL of the background, unless viewing a user profile with a background that is set",
              suggestions: ["/images/city.jpg"]
              key: :greentext,
              label: "Greentext",
              type: :boolean,
-             description: "Enables green text on lines prefixed with the > character."
+             description: "Enables green text on lines prefixed with the > character"
            },
            %{
              key: :hideFilteredStatuses,
              label: "Hide Filtered Statuses",
              type: :boolean,
-             description: "Hides filtered statuses from timelines."
+             description: "Hides filtered statuses from timelines"
            },
            %{
              key: :hideMutedPosts,
              label: "Hide Muted Posts",
              type: :boolean,
-             description: "Hides muted statuses from timelines."
+             description: "Hides muted statuses from timelines"
            },
            %{
              key: :hidePostStats,
              key: :hideSitename,
              label: "Hide Sitename",
              type: :boolean,
-             description: "Hides instance name from PleromaFE banner."
+             description: "Hides instance name from PleromaFE banner"
            },
            %{
              key: :hideUserStats,
            },
            %{
              key: :logo,
-             type: :string,
+             type: {:string, :image},
              description: "URL of the logo, defaults to Pleroma's logo",
              suggestions: ["/static/logo.png"]
            },
            %{
              key: :nsfwCensorImage,
              label: "NSFW Censor Image",
-             type: :string,
+             type: {:string, :image},
              description:
-               "URL of the image to use for hiding NSFW media attachments in the timeline.",
+               "URL of the image to use for hiding NSFW media attachments in the timeline",
              suggestions: ["/static/img/nsfw.74818f9.png"]
            },
            %{
              key: :postContentType,
              label: "Post Content Type",
              type: {:dropdown, :atom},
-             description: "Default post formatting option.",
+             description: "Default post formatting option",
              suggestions: ["text/plain", "text/html", "text/markdown", "text/bbcode"]
            },
            %{
              key: :sidebarRight,
              label: "Sidebar on Right",
              type: :boolean,
-             description: "Change alignment of sidebar and panels to the right."
+             description: "Change alignment of sidebar and panels to the right"
            },
            %{
              key: :showFeaturesPanel,
              label: "Show instance features panel",
              type: :boolean,
              description:
-               "Enables panel displaying functionality of the instance on the About page."
+               "Enables panel displaying functionality of the instance on the About page"
            },
            %{
              key: :showInstanceSpecificPanel,
          key: :mascots,
          type: {:keyword, :map},
          description:
-           "Keyword of mascots, each element must contain both an url and a mime_type key",
+           "Keyword of mascots, each element must contain both an URL and a mime_type key",
          suggestions: [
            pleroma_fox_tan: %{
              url: "/images/pleroma-fox-tan-smol.png",
        },
        %{
          key: :default_user_avatar,
-         type: :string,
-         description: "URL of the default user avatar.",
+         type: {:string, :image},
+         description: "URL of the default user avatar",
          suggestions: ["/images/avi.png"]
        }
      ]
      key: :manifest,
      type: :group,
      description:
-       "This section describe PWA manifest instance-specific values. Currently this option relate only for MastoFE",
+       "This section describe PWA manifest instance-specific values. Currently this option relate only for MastoFE.",
      children: [
        %{
          key: :icons,
        }
      ]
    },
+   %{
+     group: :pleroma,
+     key: :mrf,
+     tab: :mrf,
+     label: "MRF",
+     type: :group,
+     description: "General MRF settings",
+     children: [
+       %{
+         key: :policies,
+         type: [:module, {:list, :module}],
+         description:
+           "A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.",
+         suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF}
+       },
+       %{
+         key: :transparency,
+         label: "MRF transparency",
+         type: :boolean,
+         description:
+           "Make the content of your Message Rewrite Facility settings public (via nodeinfo)"
+       },
+       %{
+         key: :transparency_exclusions,
+         label: "MRF transparency exclusions",
+         type: {:list, :string},
+         description:
+           "Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.",
+         suggestions: [
+           "exclusion.com"
+         ]
+       }
+     ]
+   },
    %{
      group: :pleroma,
      key: :mrf_simple,
-     label: "MRF simple",
+     tab: :mrf,
+     related_policy: "Pleroma.Web.ActivityPub.MRF.SimplePolicy",
+     label: "MRF Simple",
      type: :group,
-     description: "Message Rewrite Facility",
+     description: "Simple ingress policies",
      children: [
        %{
          key: :media_removal,
          key: :federated_timeline_removal,
          type: {:list, :string},
          description:
-           "List of instances to remove from Federated (aka The Whole Known Network) Timeline",
+           "List of instances to remove from the Federated (aka The Whole Known Network) Timeline",
          suggestions: ["example.com", "*.example.com"]
        },
        %{
          description: "List of instances to only accept activities from (except deletes)",
          suggestions: ["example.com", "*.example.com"]
        },
+       %{
+         key: :followers_only,
+         type: {:list, :string},
+         description: "Force posts from the given instances to be visible by followers only",
+         suggestions: ["example.com", "*.example.com"]
+       },
        %{
          key: :report_removal,
          type: {:list, :string},
    %{
      group: :pleroma,
      key: :mrf_activity_expiration,
+     tab: :mrf,
+     related_policy: "Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy",
      label: "MRF Activity Expiration Policy",
      type: :group,
-     description: "Adds expiration to all local Create Note activities",
+     description: "Adds automatic expiration to all local activities",
      children: [
        %{
          key: :days,
          type: :integer,
-         description: "Default global expiration time for all local Create activities (in days)",
+         description: "Default global expiration time for all local activities (in days)",
          suggestions: [90, 365]
        }
      ]
    %{
      group: :pleroma,
      key: :mrf_subchain,
-     label: "MRF subchain",
+     tab: :mrf,
+     related_policy: "Pleroma.Web.ActivityPub.MRF.SubchainPolicy",
+     label: "MRF Subchain",
      type: :group,
      description:
        "This policy processes messages through an alternate pipeline when a given message matches certain criteria." <>
      children: [
        %{
          key: :match_actor,
-         type: :map,
+         type: {:map, {:list, :string}},
          description: "Matches a series of regular expressions against the actor field",
          suggestions: [
            %{
    %{
      group: :pleroma,
      key: :mrf_rejectnonpublic,
-     description:
-       "MRF RejectNonPublic settings. RejectNonPublic drops posts with non-public visibility settings.",
-     label: "MRF reject non public",
+     tab: :mrf,
+     related_policy: "Pleroma.Web.ActivityPub.MRF.RejectNonPublic",
+     description: "RejectNonPublic drops posts with non-public visibility settings.",
+     label: "MRF Reject Non Public",
      type: :group,
      children: [
        %{
    %{
      group: :pleroma,
      key: :mrf_hellthread,
-     label: "MRF hellthread",
+     tab: :mrf,
+     related_policy: "Pleroma.Web.ActivityPub.MRF.HellthreadPolicy",
+     label: "MRF Hellthread",
      type: :group,
-     description: "Block messages with too much mentions",
+     description: "Block messages with excessive user mentions",
      children: [
        %{
          key: :delist_threshold,
          type: :integer,
          description:
-           "Number of mentioned users after which the message gets delisted (the message can still be seen, " <>
-             " but it will not show up in public timelines and mentioned users won't get notifications about it). Set to 0 to disable.",
+           "Number of mentioned users after which the message gets removed from timelines and" <>
+             "disables notifications. Set to 0 to disable.",
          suggestions: [10]
        },
        %{
    %{
      group: :pleroma,
      key: :mrf_keyword,
-     label: "MRF keyword",
+     tab: :mrf,
+     related_policy: "Pleroma.Web.ActivityPub.MRF.KeywordPolicy",
+     label: "MRF Keyword",
      type: :group,
      description: "Reject or Word-Replace messages with a keyword or regex",
      children: [
        %{
          key: :reject,
-         type: [:string, :regex],
+         type: {:list, :string},
          description:
            "A list of patterns which result in message being rejected. Each pattern can be a string or a regular expression.",
          suggestions: ["foo", ~r/foo/iu]
        },
        %{
          key: :federated_timeline_removal,
-         type: [:string, :regex],
+         type: {:list, :string},
          description:
            "A list of patterns which result in message being removed from federated timelines (a.k.a unlisted). Each pattern can be a string or a regular expression.",
          suggestions: ["foo", ~r/foo/iu]
        },
        %{
          key: :replace,
-         type: [{:tuple, :string, :string}, {:tuple, :regex, :string}],
+         type: {:list, :tuple},
          description:
            "A list of tuples containing {pattern, replacement}. Each pattern can be a string or a regular expression.",
          suggestions: [{"foo", "bar"}, {~r/foo/iu, "bar"}]
    %{
      group: :pleroma,
      key: :mrf_mention,
-     label: "MRF mention",
+     tab: :mrf,
+     related_policy: "Pleroma.Web.ActivityPub.MRF.MentionPolicy",
+     label: "MRF Mention",
      type: :group,
-     description: "Block messages which mention a user",
+     description: "Block messages which mention a specific user",
      children: [
        %{
          key: :actors,
          type: {:list, :string},
-         description: "A list of actors for which any post mentioning them will be dropped.",
+         description: "A list of actors for which any post mentioning them will be dropped",
          suggestions: ["actor1", "actor2"]
        }
      ]
    %{
      group: :pleroma,
      key: :mrf_vocabulary,
-     label: "MRF vocabulary",
+     tab: :mrf,
+     related_policy: "Pleroma.Web.ActivityPub.MRF.VocabularyPolicy",
+     label: "MRF Vocabulary",
      type: :group,
      description: "Filter messages which belong to certain activity vocabularies",
      children: [
          key: :accept,
          type: {:list, :string},
          description:
-           "A list of ActivityStreams terms to accept. If empty, all supported messages are accepted",
+           "A list of ActivityStreams terms to accept. If empty, all supported messages are accepted.",
          suggestions: ["Create", "Follow", "Mention", "Announce", "Like"]
        },
        %{
          key: :reject,
          type: {:list, :string},
          description:
-           "A list of ActivityStreams terms to reject. If empty, no messages are rejected",
+           "A list of ActivityStreams terms to reject. If empty, no messages are rejected.",
          suggestions: ["Create", "Follow", "Mention", "Announce", "Like"]
        }
      ]
    # %{
    #   group: :pleroma,
    #   key: :mrf_user_allowlist,
+   #   tab: :mrf,
+   #   related_policy: "Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy",
    #   type: :map,
    #   description:
    #     "The keys in this section are the domain names that the policy should apply to." <>
        },
        %{
          key: :base_url,
+         label: "Base URL",
          type: :string,
          description:
            "The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.",
          suggestions: ["https://example.com"]
        },
+       %{
+         key: :invalidation,
+         type: :keyword,
+         descpiption: "",
+         suggestions: [
+           enabled: true,
+           provider: Pleroma.Web.MediaProxy.Invalidation.Script
+         ],
+         children: [
+           %{
+             key: :enabled,
+             type: :boolean,
+             description: "Enables media cache object invalidation."
+           },
+           %{
+             key: :provider,
+             type: :module,
+             description: "Module which will be used to purge objects from the cache.",
+             suggestions: [
+               Pleroma.Web.MediaProxy.Invalidation.Script,
+               Pleroma.Web.MediaProxy.Invalidation.Http
+             ]
+           }
+         ]
+       },
        %{
          key: :proxy_opts,
+         label: "Proxy Options",
          type: :keyword,
          description: "Options for Pleroma.ReverseProxy",
          suggestions: [
            },
            %{
              key: :http,
+             label: "HTTP",
              type: :keyword,
              description: "HTTP options",
              children: [
        %{
          key: :whitelist,
          type: {:list, :string},
-         description: "List of domains to bypass the mediaproxy",
-         suggestions: ["example.com"]
+         description: "List of hosts with scheme to bypass the mediaproxy",
+         suggestions: ["http://example.com"]
+       }
+     ]
+   },
+   %{
+     group: :pleroma,
+     key: Pleroma.Web.MediaProxy.Invalidation.Http,
+     type: :group,
+     description: "HTTP invalidate settings",
+     children: [
+       %{
+         key: :method,
+         type: :atom,
+         description: "HTTP method of request. Default: :purge"
+       },
+       %{
+         key: :headers,
+         type: {:keyword, :string},
+         description: "HTTP headers of request",
+         suggestions: [{"x-refresh", 1}]
+       },
+       %{
+         key: :options,
+         type: :keyword,
+         description: "Request options",
+         children: [
+           %{
+             key: :params,
+             type: {:map, :string}
+           }
+         ]
+       }
+     ]
+   },
+   %{
+     group: :pleroma,
+     key: Pleroma.Web.MediaProxy.Invalidation.Script,
+     type: :group,
+     description: "Script invalidate settings",
+     children: [
+       %{
+         key: :script_path,
+         type: :string,
+         description: "Path to shell script. Which will run purge cache.",
+         suggestions: ["./installation/nginx-cache-purge.sh.example"]
        }
      ]
    },
        },
        %{
          key: :ip,
+         label: "IP",
          type: :tuple,
          description: "IP address to bind to",
          suggestions: [{0, 0, 0, 0}]
        %{
          key: :dstport,
          type: :integer,
-         description: "Port advertised in urls (optional, defaults to port)",
+         description: "Port advertised in URLs (optional, defaults to port)",
          suggestions: [9999]
        }
      ]
    %{
      group: :pleroma,
      key: :activitypub,
+     label: "ActivityPub",
      type: :group,
      description: "ActivityPub-related settings",
      children: [
          key: :note_replies_output_limit,
          type: :integer,
          description:
-           "The number of Note replies' URIs to be included with outgoing federation (`5` to match Mastodon hardcoded value, `0` to disable the output)."
+           "The number of Note replies' URIs to be included with outgoing federation (`5` to match Mastodon hardcoded value, `0` to disable the output)"
        },
        %{
          key: :follow_handshake_timeout,
    %{
      group: :pleroma,
      key: :http_security,
+     label: "HTTP security",
      type: :group,
      description: "HTTP security settings",
      children: [
          key: :report_uri,
          label: "Report URI",
          type: :string,
-         description: "Adds the specified url to report-uri and report-to group in CSP header",
+         description: "Adds the specified URL to report-uri and report-to group in CSP header",
          suggestions: ["https://example.com/report-uri"]
        }
      ]
    %{
      group: :web_push_encryption,
      key: :vapid_details,
+     label: "Vapid Details",
      type: :group,
      description:
-       "Web Push Notifications configuration. You can use the mix task mix web_push.gen.keypair to generate it",
+       "Web Push Notifications configuration. You can use the mix task mix web_push.gen.keypair to generate it.",
      children: [
        %{
          key: :subject,
    },
    %{
      group: :pleroma,
+     label: "Pleroma Admin Token",
      type: :group,
      description:
-       "Allows to set a token that can be used to authenticate with the admin api without using an actual user by giving it as the `admin_token` parameter",
+       "Allows setting a token that can be used to authenticate requests with admin privileges without a normal user account token. Append the `admin_token` parameter to requests to utilize it. (Please reconsider using HTTP Basic Auth or OAuth-based authentication if possible)",
      children: [
        %{
          key: :admin_token,
          type: :string,
-         description: "Token",
-         suggestions: ["We recommend a secure random string or UUID"]
+         description: "Admin token",
+         suggestions: [
+           "Please use a high entropy string or UUID"
+         ]
        }
      ]
    },
      """,
      children: [
        %{
-         key: :verbose,
+         key: :log,
          type: {:dropdown, :atom},
          description: "Logs verbose mode",
          suggestions: [false, :error, :warn, :info, :debug]
        },
-       %{
-         key: :prune,
-         type: [:atom, :tuple],
-         description:
-           "Non-retryable jobs [pruning settings](https://github.com/sorentwo/oban#pruning)",
-         suggestions: [:disabled, {:maxlen, 1500}, {:maxage, 60 * 60}]
-       },
        %{
          key: :queues,
          type: {:keyword, :integer},
          type: {:list, :tuple},
          description: "Settings for cron background jobs",
          suggestions: [
-           {"0 0 * * *", Pleroma.Workers.Cron.ClearOauthTokenWorker},
-           {"0 * * * *", Pleroma.Workers.Cron.StatsWorker},
-           {"* * * * *", Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker},
            {"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
            {"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}
          ]
      key: :rich_media,
      type: :group,
      description:
-       "If enabled the instance will parse metadata from attached links to generate link previews.",
+       "If enabled the instance will parse metadata from attached links to generate link previews",
      children: [
        %{
          key: :enabled,
          type: :boolean,
-         description: "Enables RichMedia parsing of URLs."
+         description: "Enables RichMedia parsing of URLs"
        },
        %{
          key: :ignore_hosts,
          type: {:list, :string},
-         description: "List of hosts which will be ignored by the metadata parser.",
+         description: "List of hosts which will be ignored by the metadata parser",
          suggestions: ["accounts.google.com", "xss.website"]
        },
        %{
          key: :ignore_tld,
          label: "Ignore TLD",
          type: {:list, :string},
-         description: "List TLDs (top-level domains) which will ignore for parse metadata.",
+         description: "List TLDs (top-level domains) which will ignore for parse metadata",
          suggestions: ["local", "localdomain", "lan"]
        },
        %{
          suggestions: [
            Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl
          ]
+       },
+       %{
+         key: :failure_backoff,
+         type: :integer,
+         description:
+           "Amount of milliseconds after request failure, during which the request will not be retried.",
+         suggestions: [60_000]
        }
      ]
    },
    %{
-     group: :auto_linker,
-     key: :opts,
+     group: :pleroma,
+     key: Pleroma.Formatter,
+     label: "Auto Linker",
      type: :group,
-     description: "Configuration for the auto_linker library",
+     description:
+       "Configuration for Pleroma's link formatter which parses mentions, hashtags, and URLs.",
      children: [
        %{
          key: :class,
-         type: [:string, false],
-         description: "Specify the class to be added to the generated link. Disable to clear",
+         type: [:string, :boolean],
+         description: "Specify the class to be added to the generated link. Disable to clear.",
          suggestions: ["auto-linker", false]
        },
        %{
          key: :rel,
-         type: [:string, false],
-         description: "Override the rel attribute. Disable to clear",
+         type: [:string, :boolean],
+         description: "Override the rel attribute. Disable to clear.",
          suggestions: ["ugc", "noopener noreferrer", false]
        },
        %{
          key: :new_window,
          type: :boolean,
-         description: "Link urls will open in new window/tab"
+         description: "Link URLs will open in a new window/tab."
        },
        %{
          key: :truncate,
-         type: [:integer, false],
+         type: [:integer, :boolean],
          description:
-           "Set to a number to truncate urls longer then the number. Truncated urls will end in `..`",
+           "Set to a number to truncate URLs longer than the number. Truncated URLs will end in `...`",
          suggestions: [15, false]
        },
        %{
          key: :strip_prefix,
          type: :boolean,
-         description: "Strip the scheme prefix"
+         description: "Strip the scheme prefix."
        },
        %{
          key: :extra,
          type: :boolean,
-         description: "Link urls with rarely used schemes (magnet, ipfs, irc, etc.)"
+         description: "Link URLs with rarely used schemes (magnet, ipfs, irc, etc.)"
+       },
+       %{
+         key: :validate_tld,
+         type: [:atom, :boolean],
+         description:
+           "Set to false to disable TLD validation for URLs/emails. Can be set to :no_scheme to validate TLDs only for URLs without a scheme (e.g `example.com` will be validated, but `http://example.loki` won't)",
+         suggestions: [:no_scheme, true]
        }
      ]
    },
    },
    %{
      group: :pleroma,
-     key: Pleroma.ActivityExpiration,
+     key: Pleroma.Workers.PurgeExpiredActivity,
      type: :group,
-     description: "Expired activity settings",
+     description: "Expired activities settings",
      children: [
        %{
          key: :enabled,
          type: :boolean,
-         description: "Whether expired activities will be sent to the job queue to be deleted"
+         description: "Enables expired activities addition & deletion"
+       },
+       %{
+         key: :min_lifetime,
+         type: :integer,
+         description: "Minimum lifetime for ephemeral activity (in seconds)",
+         suggestions: [600]
        }
      ]
    },
    %{
      group: :pleroma,
+     label: "Pleroma Authenticator",
      type: :group,
      description: "Authenticator",
      children: [
    %{
      group: :pleroma,
      key: :ldap,
+     label: "LDAP",
      type: :group,
      description:
        "Use LDAP for user authentication. When a user logs in to the Pleroma instance, the name and password" <>
        },
        %{
          key: :uid,
+         label: "UID",
          type: :string,
          description:
            "LDAP attribute name to authenticate the user, e.g. when \"cn\", the filter will be \"cn=username,base\"",
      children: [
        %{
          key: :enforce_oauth_admin_scope_usage,
+         label: "Enforce OAuth admin scope usage",
          type: :boolean,
          description:
            "OAuth admin scope requirement toggle. " <>
              "If enabled, admin actions explicitly demand admin OAuth scope(s) presence in OAuth token " <>
-             "(client app must support admin scopes). If disabled and token doesn't have admin scope(s)," <>
+             "(client app must support admin scopes). If disabled and token doesn't have admin scope(s), " <>
              "`is_admin` user flag grants access to admin-specific actions."
        },
        %{
        },
        %{
          key: :oauth_consumer_template,
+         label: "OAuth consumer template",
          type: :string,
          description:
            "OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to" <>
        },
        %{
          key: :oauth_consumer_strategies,
+         label: "OAuth consumer strategies",
          type: {:list, :string},
          description:
            "The list of enabled OAuth consumer strategies. By default it's set by OAUTH_CONSUMER_STRATEGIES environment variable." <>
      children: [
        %{
          key: :logo,
-         type: :string,
+         type: {:string, :image},
          description: "A path to a custom logo. Set it to `nil` to use the default Pleroma logo.",
          suggestions: ["some/path/logo.png"]
        },
        %{
          key: :styling,
          type: :map,
-         description: "a map with color settings for email templates.",
+         description: "A map with color settings for email templates.",
          suggestions: [
            %{
              link_color: "#d8a070",
        %{
          key: :enabled,
          type: :boolean,
-         description: "enables new users admin digest email when `true`",
-         suggestions: [false]
+         description: "Enables new users admin digest email when `true`"
        }
      ]
    },
    %{
      group: :pleroma,
      key: :oauth2,
+     label: "OAuth2",
      type: :group,
      description: "Configure OAuth 2 provider capabilities",
      children: [
        %{
          key: :clean_expired_tokens,
          type: :boolean,
-         description: "Enable a background job to clean expired oauth tokens. Default: disabled."
+         description: "Enable a background job to clean expired OAuth tokens. Default: disabled."
        }
      ]
    },
        },
        %{
          key: :groups,
-         type: {:keyword, :string, {:list, :string}},
+         type: {:keyword, {:list, :string}},
          description:
            "Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the group name" <>
              " and the value is the location or array of locations. * can be used as a wildcard.",
        },
        %{
          key: :relation_id_action,
+         label: "Relation ID action",
          type: [:tuple, {:list, :tuple}],
          description: "For actions on relation with a specific user (follow, unfollow)",
          suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
        },
        %{
          key: :status_id_action,
+         label: "Status ID action",
          type: [:tuple, {:list, :tuple}],
          description:
            "For fav / unfav or reblog / unreblog actions on the same status by the same user",
    },
    %{
      group: :esshd,
+     label: "ESSHD",
      type: :group,
      description:
        "Before enabling this you must add :esshd to mix.exs as one of the extra_applications " <>
    },
    %{
      group: :mime,
+     label: "Mime Types",
      type: :group,
-     description: "Mime types",
+     description: "Mime Types settings",
      children: [
        %{
          key: :types,
    %{
      group: :pleroma,
      key: :http,
+     label: "HTTP",
      type: :group,
      description: "HTTP settings",
      children: [
    %{
      group: :pleroma,
      key: :markup,
+     label: "Markup Settings",
      type: :group,
      children: [
        %{
    %{
      group: :pleroma,
      key: :mrf_normalize_markup,
-     label: "MRF normalize markup",
+     tab: :mrf,
+     related_policy: "Pleroma.Web.ActivityPub.MRF.NormalizeMarkup",
+     label: "MRF Normalize Markup",
      description: "MRF NormalizeMarkup settings. Scrub configured hypertext markup.",
      type: :group,
      children: [
        %{
          key: :restricted_nicknames,
          type: {:list, :string},
+         description: "List of nicknames users may not register with.",
          suggestions: [
            ".well-known",
            "~",
            "users",
            "web"
          ]
+       },
+       %{
+         key: :email_blacklist,
+         type: {:list, :string},
+         description: "List of email domains users may not register with.",
+         suggestions: ["mailinator.com", "maildrop.cc"]
        }
      ]
    },
    %{
      group: :cors_plug,
+     label: "CORS plug config",
      type: :group,
      children: [
        %{
    %{
      group: :pleroma,
      key: :web_cache_ttl,
+     label: "Web cache TTL",
      type: :group,
      description:
        "The expiration time for the web responses cache. Values should be in milliseconds or `nil` to disable expiration.",
    %{
      group: :pleroma,
      key: :static_fe,
+     label: "Static FE",
      type: :group,
      description:
-       "Render profiles and posts using server-generated HTML that is viewable without using JavaScript.",
+       "Render profiles and posts using server-generated HTML that is viewable without using JavaScript",
      children: [
        %{
          key: :enabled,
        %{
          key: :post_title,
          type: :map,
-         description: "Configure title rendering.",
+         description: "Configure title rendering",
          children: [
            %{
              key: :max_length,
              type: :integer,
-             description: "Maximum number of characters before truncating title.",
+             description: "Maximum number of characters before truncating title",
              suggestions: [100]
            },
            %{
              key: :omission,
              type: :string,
-             description: "Replacement which will be used after truncating string.",
+             description: "Replacement which will be used after truncating string",
              suggestions: ["..."]
            }
          ]
    %{
      group: :pleroma,
      key: :mrf_object_age,
+     tab: :mrf,
+     related_policy: "Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy",
+     label: "MRF Object Age",
      type: :group,
-     description: "Rejects or delists posts based on their age when received.",
+     description:
+       "Rejects or delists posts based on their timestamp deviance from your server's clock.",
      children: [
        %{
          key: :threshold,
          type: {:list, :atom},
          description:
            "A list of actions to apply to the post. `:delist` removes the post from public timelines; " <>
-             "`:strip_followers` removes followers from the ActivityPub recipient list, ensuring they won't be delivered to home timelines; " <>
+             "`:strip_followers` removes followers from the ActivityPub recipient list ensuring they won't be delivered to home timelines; " <>
              "`:reject` rejects the message entirely",
          suggestions: [:delist, :strip_followers, :reject]
        }
        %{
          key: :workers,
          type: :integer,
-         description: "Number of workers to send notifications.",
+         description: "Number of workers to send notifications",
          suggestions: [3]
        },
        %{
          key: :overflow_workers,
          type: :integer,
-         description: "Maximum number of workers created if pool is empty.",
+         description: "Maximum number of workers created if pool is empty",
          suggestions: [2]
        }
      ]
      group: :pleroma,
      key: :connections_pool,
      type: :group,
-     description: "Advanced settings for `gun` connections pool",
+     description: "Advanced settings for `Gun` connections pool",
      children: [
        %{
-         key: :checkin_timeout,
+         key: :connection_acquisition_wait,
          type: :integer,
-         description: "Timeout to checkin connection from pool. Default: 250ms.",
+         description:
+           "Timeout to acquire a connection from pool. The total max time is this value multiplied by the number of retries. Default: 250ms.",
          suggestions: [250]
        },
+       %{
+         key: :connection_acquisition_retries,
+         type: :integer,
+         description:
+           "Number of attempts to acquire the connection from the pool if it is overloaded. Default: 5",
+         suggestions: [5]
+       },
        %{
          key: :max_connections,
          type: :integer,
          suggestions: [250]
        },
        %{
-         key: :retry,
+         key: :connect_timeout,
          type: :integer,
-         description:
-           "Number of retries, while `gun` will try to reconnect if connection goes down. Default: 1.",
-         suggestions: [1]
+         description: "Timeout while `gun` will wait until connection is up. Default: 5000ms.",
+         suggestions: [5000]
        },
        %{
-         key: :retry_timeout,
+         key: :reclaim_multiplier,
          type: :integer,
          description:
-           "Time between retries when `gun` will try to reconnect in milliseconds. Default: 1000ms.",
-         suggestions: [1000]
-       },
-       %{
-         key: :await_up_timeout,
-         type: :integer,
-         description: "Timeout while `gun` will wait until connection is up. Default: 5000ms.",
-         suggestions: [5000]
+           "Multiplier for the number of idle connection to be reclaimed if the pool is full. For example if the pool maxes out at 250 connections and this setting is set to 0.3, the pool will reclaim at most 75 idle connections if it's overloaded. Default: 0.1",
+         suggestions: [0.1]
        }
      ]
    },
      group: :pleroma,
      key: :pools,
      type: :group,
-     description: "Advanced settings for `gun` workers pools",
-     children: [
-       %{
-         key: :federation,
-         type: :keyword,
-         description: "Settings for federation pool.",
-         children: [
-           %{
-             key: :size,
-             type: :integer,
-             description: "Number workers in the pool.",
-             suggestions: [50]
-           },
-           %{
-             key: :max_overflow,
-             type: :integer,
-             description: "Number of additional workers if pool is under load.",
-             suggestions: [10]
-           },
-           %{
-             key: :timeout,
-             type: :integer,
-             description: "Timeout while `gun` will wait for response.",
-             suggestions: [150_000]
-           }
-         ]
-       },
-       %{
-         key: :media,
-         type: :keyword,
-         description: "Settings for media pool.",
-         children: [
-           %{
-             key: :size,
-             type: :integer,
-             description: "Number workers in the pool.",
-             suggestions: [50]
-           },
-           %{
-             key: :max_overflow,
-             type: :integer,
-             description: "Number of additional workers if pool is under load.",
-             suggestions: [10]
-           },
-           %{
-             key: :timeout,
-             type: :integer,
-             description: "Timeout while `gun` will wait for response.",
-             suggestions: [150_000]
-           }
-         ]
-       },
-       %{
-         key: :upload,
-         type: :keyword,
-         description: "Settings for upload pool.",
-         children: [
-           %{
-             key: :size,
-             type: :integer,
-             description: "Number workers in the pool.",
-             suggestions: [25]
-           },
-           %{
-             key: :max_overflow,
-             type: :integer,
-             description: "Number of additional workers if pool is under load.",
-             suggestions: [5]
-           },
-           %{
-             key: :timeout,
-             type: :integer,
-             description: "Timeout while `gun` will wait for response.",
-             suggestions: [300_000]
-           }
-         ]
-       },
-       %{
-         key: :default,
-         type: :keyword,
-         description: "Settings for default pool.",
-         children: [
-           %{
-             key: :size,
-             type: :integer,
-             description: "Number workers in the pool.",
-             suggestions: [10]
-           },
-           %{
-             key: :max_overflow,
-             type: :integer,
-             description: "Number of additional workers if pool is under load.",
-             suggestions: [2]
-           },
-           %{
-             key: :timeout,
-             type: :integer,
-             description: "Timeout while `gun` will wait for response.",
-             suggestions: [10_000]
-           }
-         ]
-       }
-     ]
+     description: "Advanced settings for `Gun` workers pools",
+     children:
+       Enum.map([:federation, :media, :upload, :default], fn pool_name ->
+         %{
+           key: pool_name,
+           type: :keyword,
+           description: "Settings for #{pool_name} pool.",
+           children: [
+             %{
+               key: :size,
+               type: :integer,
+               description: "Maximum number of concurrent requests in the pool.",
+               suggestions: [50]
+             },
+             %{
+               key: :max_waiting,
+               type: :integer,
+               description:
+                 "Maximum number of requests waiting for other requests to finish. After this number is reached, the pool will start returning errrors when a new request is made",
+               suggestions: [10]
+             },
+             %{
+               key: :recv_timeout,
+               type: :integer,
+               description: "Timeout for the pool while gun will wait for response",
+               suggestions: [10_000]
+             }
+           ]
+         }
+       end)
    },
    %{
      group: :pleroma,
      key: :hackney_pools,
      type: :group,
-     description: "Advanced settings for `hackney` connections pools",
+     description: "Advanced settings for `Hackney` connections pools",
      children: [
        %{
          key: :federation,
    %{
      group: :pleroma,
      key: :restrict_unauthenticated,
+     label: "Restrict Unauthenticated",
      type: :group,
      description:
        "Disallow viewing timelines, user profiles and statuses for unauthenticated users.",
          key: :strict,
          type: :boolean,
          description:
-           "Enables strict input validation (useful in development, not recommended in production)",
-         suggestions: [false]
+           "Enables strict input validation (useful in development, not recommended in production)"
+       }
+     ]
+   },
+   %{
+     group: :pleroma,
+     key: :instances_favicons,
+     type: :group,
+     description: "Control favicons for instances",
+     children: [
+       %{
+         key: :enabled,
+         type: :boolean,
+         description: "Allow/disallow displaying and getting instances favicons"
+       }
+     ]
+   },
+   %{
+     group: :ex_aws,
+     key: :s3,
+     type: :group,
+     descriptions: "S3 service related settings",
+     children: [
+       %{
+         key: :access_key_id,
+         type: :string,
+         description: "S3 access key ID",
+         suggestions: ["AKIAQ8UKHTGIYN7DMWWJ"]
+       },
+       %{
+         key: :secret_access_key,
+         type: :string,
+         description: "Secret access key",
+         suggestions: ["JFGt+fgH1UQ7vLUQjpW+WvjTdV/UNzVxcwn7DkaeFKtBS5LvoXvIiME4NQBsT6ZZ"]
+       },
+       %{
+         key: :host,
+         type: :string,
+         description: "S3 host",
+         suggestions: ["s3.eu-central-1.amazonaws.com"]
+       }
+     ]
+   },
+   %{
+     group: :pleroma,
+     key: :frontends,
+     type: :group,
+     description: "Installed frontends management",
+     children: [
+       %{
+         key: :primary,
+         type: :map,
+         description: "Primary frontend, the one that is served for all pages by default",
+         children: installed_frontend_options
+       },
+       %{
+         key: :admin,
+         type: :map,
+         description: "Admin frontend",
+         children: installed_frontend_options
+       },
+       %{
+         key: :available,
+         type: :map,
+         description:
+           "A map containing available frontends and parameters for their installation.",
+         children: [
+           frontend_options
+         ]
+       }
+     ]
+   },
+   %{
+     group: :pleroma,
+     key: Pleroma.Web.Preload,
+     type: :group,
+     description: "Preload-related settings",
+     children: [
+       %{
+         key: :providers,
+         type: {:list, :module},
+         description: "List of preload providers to enable",
+         suggestions: [
+           Pleroma.Web.Preload.Providers.Instance,
+           Pleroma.Web.Preload.Providers.User,
+           Pleroma.Web.Preload.Providers.Timelines,
+           Pleroma.Web.Preload.Providers.StatusNet
+         ]
        }
      ]
 +  },
 +  %{
 +    group: :pleroma,
 +    key: :majic_pool,
 +    type: :group,
 +    description: "Majic/libmagic configuration",
 +    children: [
 +      %{
 +        key: :size,
 +        type: :integer,
 +        description: "Number of majic workers to start.",
 +        suggestions: [2]
 +      }
 +    ]
    }
  ]
index d3b3e8279cadb6aa054b1a6124aa44ebe10a05f9,a5683f18c14fe04a60ca09c6aa5903b1331ea1ee..f393e497883e5569484b1b9ee60f5f1c91d6413a
@@@ -13,8 -13,8 +13,9 @@@ It assumes that you have administrativ
  * `erlang-parsetools`
  * `erlang-xmerl`
  * `git`
 +* `file-dev`
  * Development Tools
+ * `cmake`
  
  #### Optional packages used in this guide
  
@@@ -40,7 -40,7 +41,7 @@@ sudo apk upgrad
  * Install some tools, which are needed later:
  
  ```shell
- sudo apk add git build-base file-dev
 -sudo apk add git build-base cmake
++sudo apk add git build-base cmake file-dev
  ```
  
  ### Install Elixir and Erlang
@@@ -226,10 -226,7 +227,7 @@@ sudo -Hu pleroma MIX_ENV=prod mix plero
  
  #### Further reading
  
- * [Backup your instance](../administration/backup.md)
- * [Hardening your instance](../configuration/hardening.md)
- * [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
- * [Updating your instance](../administration/updating.md)
+ {! backend/installation/further_reading.include !}
  
  ## Questions
  
index 36ef3e345c347ac0bd087a60c4e9193e2f78198b,7fb69dd60bc03b39901e9dd226541cfa53fec711..99eb011ad7f33b387dccf88f1e5a14036e79cf90
@@@ -9,7 -9,7 +9,8 @@@ This guide will assume that you have ad
  * `elixir`
  * `git`
  * `base-devel`
+ * `cmake`
 +* `file`
  
  #### Optional packages used in this guide
  
@@@ -27,7 -27,7 +28,7 @@@ sudo pacman -Sy
  * Install some of the above mentioned programs:
  
  ```shell
- sudo pacman -S git base-devel elixir file
 -sudo pacman -S git base-devel elixir cmake
++sudo pacman -S git base-devel elixir cmake file
  ```
  
  ### Install PostgreSQL
@@@ -201,10 -201,7 +202,7 @@@ sudo -Hu pleroma MIX_ENV=prod mix plero
  
  #### Further reading
  
- * [Backup your instance](../administration/backup.md)
- * [Hardening your instance](../configuration/hardening.md)
- * [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
- * [Updating your instance](../administration/updating.md)
+ {! backend/installation/further_reading.include !}
  
  ## Questions
  
index 9357df5d237851f38c26f695bb05fcfbc77461ad,60c2f47e5207b3337ba8de8ac16f5cc89a179aef..58d15ce1472ffd1201b1b8c4cbce5fcc7969545d
@@@ -10,9 -10,9 +10,10 @@@ This guide will assume you are on Debia
  * `elixir` (1.8+, Follow the guide to install from the Erlang Solutions repo or use [asdf](https://github.com/asdf-vm/asdf) as the pleroma user)
  * `erlang-dev`
  * `erlang-nox`
 +* `libmagic-dev`
  * `git`
  * `build-essential`
+ * `cmake`
  
  #### Optional packages used in this guide
  
@@@ -31,7 -31,7 +32,7 @@@ sudo apt full-upgrad
  * Install some of the above mentioned programs:
  
  ```shell
- sudo apt install git build-essential postgresql postgresql-contrib libmagic-devel
 -sudo apt install git build-essential postgresql postgresql-contrib cmake
++sudo apt install git build-essential postgresql postgresql-contrib cmake libmagic-devel
  ```
  
  ### Install Elixir and Erlang
@@@ -187,10 -187,7 +188,7 @@@ sudo -Hu pleroma MIX_ENV=prod mix plero
  
  #### Further reading
  
- * [Backup your instance](../administration/backup.md)
- * [Hardening your instance](../configuration/hardening.md)
- * [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
- * [Updating your instance](../administration/updating.md)
+ {! backend/installation/further_reading.include !}
  
  ## Questions
  
index 21cd4d2adc1c747bd508d2ff3dfd93b0e96b3309,c2dd840d30f42403479289561d03408e4a380796..d98162796dff718c5543dcbebb21e546c53e09c7
@@@ -16,7 -16,7 +16,8 @@@
  - `erlang-nox`
  - `git`
  - `build-essential`
+ - `cmake`
 +- `libmagic-dev`
  
  #### このガイドで利用している追加パッケージ
  
@@@ -33,7 -33,7 +34,7 @@@ sudo apt full-upgrad
  
  * 上記に挙げたパッケージをインストールしておきます。
  ```
- sudo apt install git build-essential postgresql postgresql-contrib libmagic-dev
 -sudo apt install git build-essential postgresql postgresql-contrib cmake
++sudo apt install git build-essential postgresql postgresql-contrib cmake libmagic-dev
  ```
  
  
@@@ -176,10 -176,7 +177,7 @@@ sudo -Hu pleroma MIX_ENV=prod mix plero
  
  #### その他の設定とカスタマイズ
  
- * [Backup your instance](../administration/backup.md)
- * [Hardening your instance](../configuration/hardening.md)
- * [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
- * [Updating your instance](../administration/updating.md)
+ {! backend/installation/further_reading.include !}
  
  ## 質問ある?
  
index d143e068fe92cbc7ced392faf668b2c5663ba999,5a676380cc98872d74d7ab42266f780ef115fa81..0f7ed9d474d43d0477c31e63c2ed98ca39f518b2
@@@ -28,7 -28,7 +28,8 @@@ Gentoo quite pointedly does not come wi
  * `dev-db/postgresql`
  * `dev-lang/elixir`
  * `dev-vcs/git`
+ * `dev-util/cmake`
 +* `sys-apps/file`
  
  #### Optional ebuilds used in this guide
  
@@@ -47,7 -47,7 +48,7 @@@
  * Emerge all required the required and suggested software in one go:
  
  ```shell
-  # emerge --ask dev-db/postgresql dev-lang/elixir dev-vcs/git www-servers/nginx app-crypt/certbot app-crypt/certbot-nginx sys-apps/file
 - # emerge --ask dev-db/postgresql dev-lang/elixir dev-vcs/git www-servers/nginx app-crypt/certbot app-crypt/certbot-nginx dev-util/cmake
++ # emerge --ask dev-db/postgresql dev-lang/elixir dev-vcs/git www-servers/nginx app-crypt/certbot app-crypt/certbot-nginx dev-util/cmake sys-apps/file
  ```
  
  If you would not like to install the optional packages, remove them from this line. 
@@@ -284,10 -284,7 +285,7 @@@ If you opted to allow sudo for the `ple
  
  #### Further reading
  
- * [Backup your instance](../administration/backup.md)
- * [Hardening your instance](../configuration/hardening.md)
- * [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
- * [Updating your instance](../administration/updating.md)
+ {! backend/installation/further_reading.include !}
  
  ## Questions
  
index e086cc1c46689183132e3949f520c3ca1cef1e49,b7e3bb2ac4e2956eba5e0175ba3016fc2dab5feb..32f04a9c452970433e86fd61c3f59461844a4087
@@@ -27,17 -27,18 +27,19 @@@ Other than things bundled in the OTP re
  * PostgreSQL (also utilizes extensions in postgresql-contrib)
  * nginx (could be swapped with another reverse proxy but this guide covers only it)
  * certbot (for Let's Encrypt certificates, could be swapped with another ACME client, but this guide covers only it)
 +* libmagic/file
  
- ```sh tab="Alpine"
- echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories
- apk update
- apk add curl unzip ncurses postgresql postgresql-contrib nginx certbot file-dev
- ```
- ```sh tab="Debian/Ubuntu"
- apt install curl unzip libncurses5 postgresql postgresql-contrib nginx certbot libmagic-dev
- ```
+ === "Alpine"
+     ```
+     echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories
+     apk update
 -    apk add curl unzip ncurses postgresql postgresql-contrib nginx certbot
++    apk add curl unzip ncurses postgresql postgresql-contrib nginx certbot file-dev
+     ```
+ === "Debian/Ubuntu"
+     ```
 -    apt install curl unzip libncurses5 postgresql postgresql-contrib nginx certbot
++    apt install curl unzip libncurses5 postgresql postgresql-contrib nginx certbot libmagic-dev
+     ```
  
  ## Setup
  ### Configuring PostgreSQL
  
  RUM indexes are an alternative indexing scheme that is not included in PostgreSQL by default. You can read more about them on the [Configuration page](../configuration/cheatsheet.md#rum-indexing-for-full-text-search). They are completely optional and most of the time are not worth it, especially if you are running a single user instance (unless you absolutely need ordered search results).
  
- ```sh tab="Alpine"
- apk add git build-base postgresql-dev
- git clone https://github.com/postgrespro/rum /tmp/rum
- cd /tmp/rum
- make USE_PGXS=1
- make USE_PGXS=1 install
- cd
- rm -r /tmp/rum
- ```
- ```sh tab="Debian/Ubuntu"
- # Available only on Buster/19.04
- apt install postgresql-11-rum
- ```
+ === "Alpine"
+     ```
+     apk add git build-base postgresql-dev
+     git clone https://github.com/postgrespro/rum /tmp/rum
+     cd /tmp/rum
+     make USE_PGXS=1
+     make USE_PGXS=1 install
+     cd
+     rm -r /tmp/rum
+     ```
+ === "Debian/Ubuntu"
+     ```
+     # Available only on Buster/19.04
+     apt install postgresql-11-rum
+     ```
  
  #### (Optional) Performance configuration
  It is encouraged to check [Optimizing your PostgreSQL performance](../configuration/postgresql.md) document, for tips on PostgreSQL tuning.
  
- ```sh tab="Alpine"
- rc-service postgresql restart
- ```
+ === "Alpine"
+     ```
+     rc-service postgresql restart
+     ```
  
- ```sh tab="Debian/Ubuntu"
- systemctl restart postgresql
- ```
+ === "Debian/Ubuntu"
+     ```
+     systemctl restart postgresql
+     ```
  
  If you are using PostgreSQL 12 or higher, add this to your Ecto database configuration
  
@@@ -152,14 -157,16 +158,16 @@@ certbot certonly --standalone --preferr
  
  The location of nginx configs is dependent on the distro
  
- ```sh tab="Alpine"
- cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/conf.d/pleroma.conf
- ```
+ === "Alpine"
+     ```
+     cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/conf.d/pleroma.conf
+     ```
  
- ```sh tab="Debian/Ubuntu"
- cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/sites-available/pleroma.conf
- ln -s /etc/nginx/sites-available/pleroma.conf /etc/nginx/sites-enabled/pleroma.conf
- ```
+ === "Debian/Ubuntu"
+     ```
+     cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/sites-available/pleroma.conf
+     ln -s /etc/nginx/sites-available/pleroma.conf /etc/nginx/sites-enabled/pleroma.conf
+     ```
  
  If your distro does not have either of those you can append `include /etc/nginx/pleroma.conf` to the end of the http section in /etc/nginx/nginx.conf and
  ```sh
@@@ -176,35 -183,39 +184,39 @@@ nginx -
  ```
  #### Start nginx
  
- ```sh tab="Alpine"
- rc-service nginx start
- ```
+ === "Alpine"
+     ```
+     rc-service nginx start
+     ```
  
- ```sh tab="Debian/Ubuntu"
- systemctl start nginx
- ```
+ === "Debian/Ubuntu"
+     ```
+     systemctl start nginx
+     ```
  
  At this point if you open your (sub)domain in a browser you should see a 502 error, that's because Pleroma is not started yet.
  
  ### Setting up a system service
  
- ```sh tab="Alpine"
- # Copy the service into a proper directory
- cp /opt/pleroma/installation/init.d/pleroma /etc/init.d/pleroma
+ === "Alpine"
+     ```
+     # Copy the service into a proper directory
+     cp /opt/pleroma/installation/init.d/pleroma /etc/init.d/pleroma
  
- # Start pleroma and enable it on boot
- rc-service pleroma start
- rc-update add pleroma
- ```
    # Start pleroma and enable it on boot
    rc-service pleroma start
    rc-update add pleroma
    ```
  
- ```sh tab="Debian/Ubuntu"
- # Copy the service into a proper directory
- cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service
+ === "Debian/Ubuntu"
+     ```
+     # Copy the service into a proper directory
+     cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service
  
- # Start pleroma and enable it on boot
- systemctl start pleroma
- systemctl enable pleroma
- ```
    # Start pleroma and enable it on boot
    systemctl start pleroma
    systemctl enable pleroma
    ```
  
  If everything worked, you should see Pleroma-FE when visiting your domain. If that didn't happen, try reviewing the installation steps, starting Pleroma in the foreground and seeing if there are any errrors.
  
@@@ -224,43 -235,45 +236,45 @@@ $EDITOR path-to-nginx-confi
  nginx -t
  ```
  
- ```sh tab="Alpine"
- # Restart nginx
- rc-service nginx restart
+ === "Alpine"
+     ```
+     # Restart nginx
+     rc-service nginx restart
  
- # Start the cron daemon and make it start on boot
- rc-service crond start
- rc-update add crond
    # Start the cron daemon and make it start on boot
    rc-service crond start
    rc-update add crond
  
- # Ensure the webroot menthod and post hook is working
- certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --dry-run --post-hook 'rc-service nginx reload'
    # Ensure the webroot menthod and post hook is working
    certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --dry-run --post-hook 'rc-service nginx reload'
  
- # Add it to the daily cron
- echo '#!/bin/sh
- certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --post-hook "rc-service nginx reload"
- ' > /etc/periodic/daily/renew-pleroma-cert
- chmod +x /etc/periodic/daily/renew-pleroma-cert
    # Add it to the daily cron
    echo '#!/bin/sh
    certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --post-hook "rc-service nginx reload"
    ' > /etc/periodic/daily/renew-pleroma-cert
    chmod +x /etc/periodic/daily/renew-pleroma-cert
  
- # If everything worked the output should contain /etc/cron.daily/renew-pleroma-cert
- run-parts --test /etc/periodic/daily
- ```
    # If everything worked the output should contain /etc/cron.daily/renew-pleroma-cert
    run-parts --test /etc/periodic/daily
    ```
  
- ```sh tab="Debian/Ubuntu"
- # Restart nginx
- systemctl restart nginx
+ === "Debian/Ubuntu"
+     ```
+     # Restart nginx
+     systemctl restart nginx
  
- # Ensure the webroot menthod and post hook is working
- certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --dry-run --post-hook 'systemctl reload nginx'
    # Ensure the webroot menthod and post hook is working
    certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --dry-run --post-hook 'systemctl reload nginx'
  
- # Add it to the daily cron
- echo '#!/bin/sh
- certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --post-hook "systemctl reload nginx"
- ' > /etc/cron.daily/renew-pleroma-cert
- chmod +x /etc/cron.daily/renew-pleroma-cert
    # Add it to the daily cron
    echo '#!/bin/sh
    certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --post-hook "systemctl reload nginx"
    ' > /etc/cron.daily/renew-pleroma-cert
    chmod +x /etc/cron.daily/renew-pleroma-cert
  
- # If everything worked the output should contain /etc/cron.daily/renew-pleroma-cert
- run-parts --test /etc/cron.daily
- ```
    # If everything worked the output should contain /etc/cron.daily/renew-pleroma-cert
    run-parts --test /etc/cron.daily
    ```
  
  ## Create your first user and set as admin
  ```sh
@@@ -271,10 -284,7 +285,7 @@@ This will create an account withe the u
  
  ## Further reading
  
- * [Backup your instance](../administration/backup.md)
- * [Hardening your instance](../configuration/hardening.md)
- * [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
- * [Updating your instance](../administration/updating.md)
+ {! backend/installation/further_reading.include !}
  
  ## Questions
  
index 9c74fa00eba918b5c16d3a1c4fee28e4e7322ee6,c39e24919c4c76e4ce0c682f99767e71106ba234..cd7a856d0c032c01aeaffaef1881f600926ca212
@@@ -22,26 -22,42 +22,42 @@@ defmodule Pleroma.Application d
    def repository, do: @repository
  
    def user_agent do
-     case Config.get([:http, :user_agent], :default) do
-       :default ->
-         info = "#{Pleroma.Web.base_url()} <#{Config.get([:instance, :email], "")}>"
-         named_version() <> "; " <> info
-       custom ->
-         custom
+     if Process.whereis(Pleroma.Web.Endpoint) do
+       case Config.get([:http, :user_agent], :default) do
+         :default ->
+           info = "#{Pleroma.Web.base_url()} <#{Config.get([:instance, :email], "")}>"
+           named_version() <> "; " <> info
+         custom ->
+           custom
+       end
+     else
+       # fallback, if endpoint is not started yet
+       "Pleroma Data Loader"
      end
    end
  
    # See http://elixir-lang.org/docs/stable/elixir/Application.html
    # for more information on OTP Applications
    def start(_type, _args) do
-     Pleroma.Config.Holder.save_default()
+     # Scrubbers are compiled at runtime and therefore will cause a conflict
+     # every time the application is restarted, so we disable module
+     # conflicts at runtime
+     Code.compiler_options(ignore_module_conflict: true)
+     # Disable warnings_as_errors at runtime, it breaks Phoenix live reload
+     # due to protocol consolidation warnings
+     Code.compiler_options(warnings_as_errors: false)
+     Pleroma.Telemetry.Logger.attach()
+     Config.Holder.save_default()
      Pleroma.HTML.compile_scrubbers()
+     Pleroma.Config.Oban.warn()
      Config.DeprecationWarnings.warn()
      Pleroma.Plugs.HTTPSecurityPlug.warn_if_disabled()
-     Pleroma.Repo.check_migrations_applied!()
+     Pleroma.ApplicationRequirements.verify!()
      setup_instrumenters()
      load_custom_modules()
+     check_system_commands()
+     Pleroma.Docs.JSON.compile()
  
      adapter = Application.get_env(:tesla, :adapter)
  
@@@ -80,7 -96,6 +96,7 @@@
          [
            Pleroma.Stats,
            Pleroma.JobQueueMonitor,
 +          {Majic.Pool, [name: Pleroma.MajicPool, pool_size: Config.get([:majic_pool, :size], 2)]},
            {Oban, Config.get(Oban)}
          ] ++
          task_children(@env) ++
        build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500),
        build_cachex("web_resp", limit: 2500),
        build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10),
-       build_cachex("failed_proxy_url", limit: 2500)
+       build_cachex("failed_proxy_url", limit: 2500),
+       build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000)
      ]
    end
  
    defp seconds_valid_interval,
      do: :timer.seconds(Config.get!([Pleroma.Captcha, :seconds_valid]))
  
-   defp build_cachex(type, opts),
+   @spec build_cachex(String.t(), keyword()) :: map()
+   def build_cachex(type, opts),
      do: %{
        id: String.to_atom("cachex_" <> type),
        start: {Cachex, :start_link, [String.to_atom(type <> "_cache"), opts]},
  
    # start hackney and gun pools in tests
    defp http_children(_, :test) do
-     hackney_options = Config.get([:hackney_pools, :federation])
-     hackney_pool = :hackney_pool.child_spec(:federation, hackney_options)
-     [hackney_pool, Pleroma.Pool.Supervisor]
+     http_children(Tesla.Adapter.Hackney, nil) ++ http_children(Tesla.Adapter.Gun, nil)
    end
  
    defp http_children(Tesla.Adapter.Hackney, _) do
      end
    end
  
-   defp http_children(Tesla.Adapter.Gun, _), do: [Pleroma.Pool.Supervisor]
+   defp http_children(Tesla.Adapter.Gun, _) do
+     Pleroma.Gun.ConnectionPool.children() ++
+       [{Task, &Pleroma.HTTP.AdapterHelper.Gun.limiter_setup/0}]
+   end
  
    defp http_children(_, _), do: []
+   defp check_system_commands do
+     filters = Config.get([Pleroma.Upload, :filters])
+     check_filter = fn filter, command_required ->
+       with true <- filter in filters,
+            false <- Pleroma.Utils.command_available?(command_required) do
+         Logger.error(
+           "#{filter} is specified in list of Pleroma.Upload filters, but the #{command_required} command is not found"
+         )
+       end
+     end
+     check_filter.(Pleroma.Upload.Filters.Exiftool, "exiftool")
+     check_filter.(Pleroma.Upload.Filters.Mogrify, "mogrify")
+     check_filter.(Pleroma.Upload.Filters.Mogrifun, "mogrify")
+   end
  end
diff --combined lib/pleroma/upload.ex
index a0ba2f4c0b1c7f9cb3ba34c693124811346b3cb0,015c875938967e3fed944f7eb21b46a85274cb96..db2cc1dae278009701222528e3bd159a7b7a494e
@@@ -56,14 -56,26 +56,27 @@@ defmodule Pleroma.Upload d
          }
    defstruct [:id, :name, :tempfile, :content_type, :path]
  
+   defp get_description(opts, upload) do
+     case {opts[:description], Pleroma.Config.get([Pleroma.Upload, :default_description])} do
+       {description, _} when is_binary(description) -> description
+       {_, :filename} -> upload.name
+       {_, str} when is_binary(str) -> str
+       _ -> ""
+     end
+   end
    @spec store(source, options :: [option()]) :: {:ok, Map.t()} | {:error, any()}
 +  @doc "Store a file. If using a `Plug.Upload{}` as the source, be sure to use `Majic.Plug` to ensure its content_type and filename is correct."
    def store(upload, opts \\ []) do
      opts = get_opts(opts)
  
      with {:ok, upload} <- prepare_upload(upload, opts),
           upload = %__MODULE__{upload | path: upload.path || "#{upload.id}/#{upload.name}"},
           {:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload),
+          description = get_description(opts, upload),
+          {_, true} <-
+            {:description_limit,
+             String.length(description) <= Pleroma.Config.get([:instance, :description_limit])},
           {:ok, url_spec} <- Pleroma.Uploaders.Uploader.put_file(opts.uploader, upload) do
        {:ok,
         %{
               "href" => url_from_spec(upload, opts.base_url, url_spec)
             }
           ],
-          "name" => Map.get(opts, :description) || upload.name
+          "name" => description
         }}
      else
+       {:description_limit, _} ->
+         {:error, :description_too_long}
        {:error, error} ->
          Logger.error(
            "#{__MODULE__} store (using #{inspect(opts.uploader)}) failed: #{inspect(error)}"
    end
  
    defp prepare_upload(%Plug.Upload{} = file, opts) do
 -    with :ok <- check_file_size(file.path, opts.size_limit),
 -         {:ok, content_type, name} <- Pleroma.MIME.file_mime_type(file.path, file.filename) do
 +    with :ok <- check_file_size(file.path, opts.size_limit) do
        {:ok,
         %__MODULE__{
           id: UUID.generate(),
 -         name: name,
 +         name: file.filename,
           tempfile: file.path,
 -         content_type: content_type
 +         content_type: file.content_type
         }}
      end
    end
    defp prepare_upload(%{img: "data:image/" <> image_data}, opts) do
      parsed = Regex.named_captures(~r/(?<filetype>jpeg|png|gif);base64,(?<data>.*)/, image_data)
      data = Base.decode64!(parsed["data"], ignore: :whitespace)
 -    hash = String.downcase(Base.encode16(:crypto.hash(:sha256, data)))
 +    hash = Base.encode16(:crypto.hash(:sha256, data), lower: true)
  
      with :ok <- check_binary_size(data, opts.size_limit),
           tmp_path <- tempfile_for_image(data),
 -         {:ok, content_type, name} <-
 -           Pleroma.MIME.bin_mime_type(data, hash <> "." <> parsed["filetype"]) do
 +         {:ok, %{mime_type: content_type}} <-
 +           Majic.perform({:bytes, data}, pool: Pleroma.MajicPool),
 +         [ext | _] <- MIME.extensions(content_type) do
        {:ok,
         %__MODULE__{
           id: UUID.generate(),
 -         name: name,
 +         name: hash <> "." <> ext,
           tempfile: tmp_path,
           content_type: content_type
         }}
  
    # For Mix.Tasks.MigrateLocalUploads
    defp prepare_upload(%__MODULE__{tempfile: path} = upload, _opts) do
 -    with {:ok, content_type} <- Pleroma.MIME.file_mime_type(path) do
 +    with {:ok, %{mime_type: content_type}} <- Majic.perform(path, pool: Pleroma.MajicPool) do
        {:ok, %__MODULE__{upload | content_type: content_type}}
      end
    end
index e2a5fb9e96ac88b03e678eba25d4fa0efbae79fc,732c442710bb62bb29549bdfbd3bb13a146c6d87..27bf6347d3ad683c9243d5034c1405b2e88eab42
@@@ -45,8 -45,6 +45,8 @@@ defmodule Pleroma.Web.ActivityPub.Activ
      when action in [:read_inbox, :update_outbox, :whoami, :upload_media]
    )
  
 +  plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:upload_media])
 +
    plug(
      Pleroma.Plugs.Cache,
      [query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2]
  
    defp handle_user_activity(
           %User{} = user,
-          %{"type" => "Create", "object" => %{"type" => "Note"}} = params
+          %{"type" => "Create", "object" => %{"type" => "Note"} = object} = params
         ) do
-     object =
-       params["object"]
-       |> Map.merge(Map.take(params, ["to", "cc"]))
-       |> Map.put("attributedTo", user.ap_id())
-       |> Transmogrifier.fix_object()
-     ActivityPub.create(%{
-       to: params["to"],
-       actor: user,
-       context: object["context"],
-       object: object,
-       additional: Map.take(params, ["cc"])
-     })
+     content = if is_binary(object["content"]), do: object["content"], else: ""
+     name = if is_binary(object["name"]), do: object["name"], else: ""
+     summary = if is_binary(object["summary"]), do: object["summary"], else: ""
+     length = String.length(content <> name <> summary)
+     if length > Pleroma.Config.get([:instance, :limit]) do
+       {:error, dgettext("errors", "Note is over the character limit")}
+     else
+       object =
+         object
+         |> Map.merge(Map.take(params, ["to", "cc"]))
+         |> Map.put("attributedTo", user.ap_id())
+         |> Transmogrifier.fix_object()
+       ActivityPub.create(%{
+         to: params["to"],
+         actor: user,
+         context: object["context"],
+         object: object,
+         additional: Map.take(params, ["cc"])
+       })
+     end
    end
  
    defp handle_user_activity(%User{} = user, %{"type" => "Delete"} = params) do
      {new_user, for_user}
    end
  
-   # TODO: Add support for "object" field
    @doc """
    Endpoint based on <https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload>
  
    Response:
    - HTTP Code: 201 Created
    - HTTP Body: ActivityPub object to be inserted into another's `attachment` field
+   Note: Will not point to a URL with a `Location` header because no standalone Activity has been created.
    """
    def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do
      with {:ok, object} <-
index c76cbfc488cff69fb2dc2eea393821345be86f2d,563edded70d0bcf32513fbbaab293faa3488e844..a0ed41d96708eff28d7cfb2e803acade5a7a5a55
@@@ -8,7 -8,6 +8,6 @@@ defmodule Pleroma.Web.PleromaAPI.Accoun
    import Pleroma.Web.ControllerHelper,
      only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2]
  
-   alias Ecto.Changeset
    alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
    alias Pleroma.Plugs.OAuthScopesPlug
    alias Pleroma.Plugs.RateLimiter
  
    require Pleroma.Constants
  
 +  plug(
 +    Majic.Plug,
 +    [pool: Pleroma.MajicPool] when action in [:update_avatar, :update_background, :update_banner]
 +  )
 +
    plug(
      OpenApiSpex.Plug.PutApiSpec,
      [module: Pleroma.Web.ApiSpec] when action == :confirmation_resend
      %{scopes: ["follow", "write:follows"]} when action in [:subscribe, :unsubscribe]
    )
  
-   plug(
-     OAuthScopesPlug,
-     %{scopes: ["write:accounts"]}
-     # Note: the following actions are not permission-secured in Mastodon:
-     when action in [
-            :update_avatar,
-            :update_banner,
-            :update_background
-          ]
-   )
    plug(
      OAuthScopesPlug,
      %{scopes: ["read:favourites"], fallback: :proceed_unauthenticated} when action == :favourites
      end
    end
  
-   @doc "PATCH /api/v1/pleroma/accounts/update_avatar"
-   def update_avatar(%{assigns: %{user: user}, body_params: %{img: ""}} = conn, _) do
-     {:ok, _user} =
-       user
-       |> Changeset.change(%{avatar: nil})
-       |> User.update_and_set_cache()
-     json(conn, %{url: nil})
-   end
-   def update_avatar(%{assigns: %{user: user}, body_params: params} = conn, _params) do
-     {:ok, %{data: data}} = ActivityPub.upload(params, type: :avatar)
-     {:ok, _user} = user |> Changeset.change(%{avatar: data}) |> User.update_and_set_cache()
-     %{"url" => [%{"href" => href} | _]} = data
-     json(conn, %{url: href})
-   end
-   @doc "PATCH /api/v1/pleroma/accounts/update_banner"
-   def update_banner(%{assigns: %{user: user}, body_params: %{banner: ""}} = conn, _) do
-     with {:ok, _user} <- User.update_banner(user, %{}) do
-       json(conn, %{url: nil})
-     end
-   end
-   def update_banner(%{assigns: %{user: user}, body_params: params} = conn, _) do
-     with {:ok, object} <- ActivityPub.upload(%{img: params[:banner]}, type: :banner),
-          {:ok, _user} <- User.update_banner(user, object.data) do
-       %{"url" => [%{"href" => href} | _]} = object.data
-       json(conn, %{url: href})
-     end
-   end
-   @doc "PATCH /api/v1/pleroma/accounts/update_background"
-   def update_background(%{assigns: %{user: user}, body_params: %{img: ""}} = conn, _) do
-     with {:ok, _user} <- User.update_background(user, %{}) do
-       json(conn, %{url: nil})
-     end
-   end
-   def update_background(%{assigns: %{user: user}, body_params: params} = conn, _) do
-     with {:ok, object} <- ActivityPub.upload(params, type: :background),
-          {:ok, _user} <- User.update_background(user, object.data) do
-       %{"url" => [%{"href" => href} | _]} = object.data
-       json(conn, %{url: href})
-     end
-   end
    @doc "GET /api/v1/pleroma/accounts/:id/favourites"
    def favourites(%{assigns: %{account: %{hide_favorites: true}}} = conn, _params) do
      render_error(conn, :forbidden, "Can't get favorites")
diff --combined mix.exs
index 8fcbaa34eb3cf23f6e1e73a8cec70eb2ef10ce84,18f748672af60eff8bda89e822fa43307a0277bf..d2a40bcc8a94eff625690d7287be86be6f32c7f7
+++ b/mix.exs
@@@ -4,8 -4,8 +4,8 @@@ defmodule Pleroma.Mixfile d
    def project do
      [
        app: :pleroma,
-       version: version("2.0.50"),
-       elixir: "~> 1.8",
+       version: version("2.1.50"),
+       elixir: "~> 1.9",
        elixirc_paths: elixirc_paths(Mix.env()),
        compilers: [:phoenix, :gettext] ++ Mix.compilers(),
        elixirc_options: [warnings_as_errors: warnings_as_errors(Mix.env())],
@@@ -90,8 -90,6 +90,6 @@@
    defp elixirc_paths(_), do: ["lib"]
  
    defp warnings_as_errors(:prod), do: false
-   # Uncomment this if you need testing configurable_from_database logic
-   # defp warnings_as_errors(:dev), do: false
    defp warnings_as_errors(_), do: true
  
    # Specifies OAuth dependencies.
    # Type `mix help deps` for examples and options.
    defp deps do
      [
-       {:phoenix, "~> 1.4.8"},
-       {:tzdata, "~> 0.5.21"},
-       {:plug_cowboy, "~> 2.0"},
+       {:phoenix, "~> 1.4.17"},
+       {:tzdata, "~> 1.0.3"},
+       {:plug_cowboy, "~> 2.3"},
        {:phoenix_pubsub, "~> 1.1"},
        {:phoenix_ecto, "~> 4.0"},
        {:ecto_enum, "~> 1.4"},
-       {:ecto_sql, "~> 3.3.2"},
-       {:postgrex, ">= 0.13.5"},
-       {:oban, "~> 1.2"},
-       {:gettext, "~> 0.15"},
-       {:pbkdf2_elixir, "~> 1.0"},
-       {:bcrypt_elixir, "~> 2.0"},
+       {:ecto_sql, "~> 3.4.4"},
+       {:postgrex, ">= 0.15.5"},
+       {:oban, "~> 2.0.0"},
+       {:gettext, "~> 0.18"},
+       {:pbkdf2_elixir, "~> 1.2"},
+       {:bcrypt_elixir, "~> 2.2"},
        {:trailing_format_plug, "~> 0.0.7"},
-       {:fast_sanitize, "~> 0.1"},
+       {:fast_sanitize, "~> 0.2.0"},
        {:html_entities, "~> 0.5", override: true},
-       {:phoenix_html, "~> 2.10"},
-       {:calendar, "~> 0.17.4"},
+       {:phoenix_html, "~> 2.14"},
+       {:calendar, "~> 1.0"},
        {:cachex, "~> 3.2"},
        {:poison, "~> 3.0", override: true},
-       # {:tesla, "~> 1.3", override: true},
        {:tesla,
-        git: "https://git.pleroma.social/pleroma/elixir-libraries/tesla.git",
-        ref: "61b7503cef33f00834f78ddfafe0d5d9dec2270b",
+        git: "https://github.com/teamon/tesla/",
+        ref: "9f7261ca49f9f901ceb73b60219ad6f8a9f6aa30",
         override: true},
        {:castore, "~> 0.1"},
-       {:cowlib, "~> 2.8", override: true},
+       {:cowlib, "~> 2.9", override: true},
        {:gun,
-        github: "ninenines/gun", ref: "e1a69b36b180a574c0ac314ced9613fdd52312cc", override: true},
-       {:jason, "~> 1.0"},
-       {:mogrify, "~> 0.6.1"},
+        github: "ninenines/gun", ref: "921c47146b2d9567eac7e9a4d2ccc60fffd4f327", override: true},
+       {:jason, "~> 1.2"},
+       {:mogrify, "~> 0.7.4"},
        {:ex_aws, "~> 2.1"},
        {:ex_aws_s3, "~> 2.0"},
        {:sweet_xml, "~> 0.6.6"},
-       {:earmark, "~> 1.3"},
+       {:earmark, "1.4.3"},
        {:bbcode_pleroma, "~> 0.2.0"},
-       {:ex_machina, "~> 2.3", only: :test},
-       {:credo, "~> 1.1.0", only: [:dev, :test], runtime: false},
-       {:mock, "~> 0.3.3", only: :test},
        {:crypt,
-        git: "https://github.com/msantos/crypt", ref: "f63a705f92c26955977ee62a313012e309a4d77a"},
-       {:cors_plug, "~> 1.5"},
-       {:ex_doc, "~> 0.21", only: :dev, runtime: false},
-       {:web_push_encryption, "~> 0.2.1"},
-       {:swoosh, "~> 0.23.2"},
-       {:phoenix_swoosh, "~> 0.2"},
+        git: "https://github.com/msantos/crypt.git",
+        ref: "f63a705f92c26955977ee62a313012e309a4d77a"},
+       {:cors_plug, "~> 2.0"},
+       {:web_push_encryption, "~> 0.3"},
+       {:swoosh, "~> 1.0"},
+       {:phoenix_swoosh, "~> 0.3"},
        {:gen_smtp, "~> 0.13"},
-       {:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test},
        {:ex_syslogger, "~> 1.4"},
-       {:floki, "~> 0.25"},
-       {:timex, "~> 3.5"},
+       {:floki, "~> 0.27"},
+       {:timex, "~> 3.6"},
        {:ueberauth, "~> 0.4"},
-       {:auto_linker,
-        git: "https://git.pleroma.social/pleroma/auto_linker.git",
-        ref: "95e8188490e97505c56636c1379ffdf036c1fdde"},
-       {:http_signatures,
-        git: "https://git.pleroma.social/pleroma/http_signatures.git",
-        ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"},
+       {:linkify, "~> 0.2.0"},
+       {:http_signatures, "~> 0.1.0"},
        {:telemetry, "~> 0.3"},
        {:poolboy, "~> 1.5"},
+       {:prometheus, "~> 4.6"},
        {:prometheus_ex, "~> 3.0"},
        {:prometheus_plugs, "~> 1.1"},
        {:prometheus_phoenix, "~> 1.3"},
        {:quack, "~> 0.1.1"},
        {:joken, "~> 2.0"},
        {:benchee, "~> 1.0"},
-       {:pot, "~> 0.10.2"},
+       {:pot, "~> 0.11"},
        {:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)},
        {:ex_const, "~> 0.2"},
        {:plug_static_index_html, "~> 1.0.0"},
-       {:excoveralls, "~> 0.12.1", only: :test},
        {:flake_id, "~> 0.1.0"},
+       {:concurrent_limiter,
+        git: "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git",
+        ref: "d81be41024569330f296fc472e24198d7499ba78"},
        {:remote_ip,
         git: "https://git.pleroma.social/pleroma/remote_ip.git",
         ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"},
        {:captcha,
         git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git",
         ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"},
-       {:mox, "~> 0.5", only: :test},
        {:restarter, path: "./restarter"},
 +      {:majic, git: "https://github.com/hrefhref/majic", branch: "develop"},
        {:open_api_spex,
         git: "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git",
-        ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"}
+        ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"},
+       ## dev & test
+       {:ex_doc, "~> 0.22", only: :dev, runtime: false},
+       {:ex_machina, "~> 2.4", only: :test},
+       {:credo, "~> 1.4", only: [:dev, :test], runtime: false},
+       {:mock, "~> 0.3.5", only: :test},
+       # temporary downgrade for excoveralls, hackney until hackney max_connections bug will be fixed
+       {:excoveralls, "0.12.3", only: :test},
+       {:hackney, "1.15.2", override: true},
+       {:mox, "~> 0.5", only: :test},
+       {:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test}
      ] ++ oauth_deps()
    end
  
        "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
        "ecto.reset": ["ecto.drop", "ecto.setup"],
        test: ["ecto.create --quiet", "ecto.migrate", "test"],
-       docs: ["pleroma.docs", "docs"]
+       docs: ["pleroma.docs", "docs"],
+       analyze: ["credo --strict --only=warnings,todo,fixme,consistency,readability"]
      ]
    end
  
    defp version(version) do
      identifier_filter = ~r/[^0-9a-z\-]+/i
  
-     # Pre-release version, denoted from patch version with a hyphen
-     {tag, tag_err} =
-       System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true)
-     {describe, describe_err} = System.cmd("git", ["describe", "--tags", "--abbrev=8"])
-     {commit_hash, commit_hash_err} = System.cmd("git", ["rev-parse", "--short", "HEAD"])
+     git_available? = match?({_output, 0}, System.cmd("sh", ["-c", "command -v git"]))
  
      git_pre_release =
-       cond do
-         tag_err == 0 and describe_err == 0 ->
-           describe
-           |> String.trim()
-           |> String.replace(String.trim(tag), "")
-           |> String.trim_leading("-")
-           |> String.trim()
+       if git_available? do
+         {tag, tag_err} =
+           System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true)
+         {describe, describe_err} = System.cmd("git", ["describe", "--tags", "--abbrev=8"])
+         {commit_hash, commit_hash_err} = System.cmd("git", ["rev-parse", "--short", "HEAD"])
  
-         commit_hash_err == 0 ->
-           "0-g" <> String.trim(commit_hash)
+         # Pre-release version, denoted from patch version with a hyphen
+         cond do
+           tag_err == 0 and describe_err == 0 ->
+             describe
+             |> String.trim()
+             |> String.replace(String.trim(tag), "")
+             |> String.trim_leading("-")
+             |> String.trim()
  
-         true ->
-           ""
+           commit_hash_err == 0 ->
+             "0-g" <> String.trim(commit_hash)
+           true ->
+             nil
+         end
        end
  
      # Branch name as pre-release version component, denoted with a dot
      branch_name =
-       with {branch_name, 0} <- System.cmd("git", ["rev-parse", "--abbrev-ref", "HEAD"]),
+       with true <- git_available?,
+            {branch_name, 0} <- System.cmd("git", ["rev-parse", "--abbrev-ref", "HEAD"]),
             branch_name <- String.trim(branch_name),
             branch_name <- System.get_env("PLEROMA_BUILD_BRANCH") || branch_name,
             true <-
  
          branch_name
        else
-         _ -> "stable"
+         _ -> ""
        end
  
      build_name =
diff --combined mix.lock
index b69f7eb8e72674404a3fb500d0af0bcb459f1c21,a28c47017a950e56f733b9fd343e8dc6a31dbf2e..96d10641f9a5f77b3e51d67c03254edb5420f2ea
+++ b/mix.lock
@@@ -1,6 -1,5 +1,5 @@@
  %{
    "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"},
-   "auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "95e8188490e97505c56636c1379ffdf036c1fdde", [ref: "95e8188490e97505c56636c1379ffdf036c1fdde"]},
    "base62": {:hex, :base62, "1.2.1", "4866763e08555a7b3917064e9eef9194c41667276c51b59de2bc42c6ea65f806", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "3b29948de2013d3f93aa898c884a9dff847e7aec75d9d6d8c1dc4c61c2716c42"},
    "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
    "bbcode": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/bbcode.git", "f2d267675e9a7e1ad1ea9beb4cc23382762b66c2", [ref: "v0.2.0"]},
    "benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "3ad58ae787e9c7c94dd7ceda3b587ec2c64604563e049b2a0e8baafae832addb"},
    "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
    "cachex": {:hex, :cachex, "3.2.0", "a596476c781b0646e6cb5cd9751af2e2974c3e0d5498a8cab71807618b74fe2f", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "aef93694067a43697ae0531727e097754a9e992a1e7946296f5969d6dd9ac986"},
-   "calendar": {:hex, :calendar, "0.17.6", "ec291cb2e4ba499c2e8c0ef5f4ace974e2f9d02ae9e807e711a9b0c7850b9aee", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "738d0e17a93c2ccfe4ddc707bdc8e672e9074c8569498483feb1c4530fb91b2b"},
+   "calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"},
    "captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "e0f16822d578866e186a0974d65ad58cddc1e2ab", [ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"]},
-   "castore": {:hex, :castore, "0.1.5", "591c763a637af2cc468a72f006878584bc6c306f8d111ef8ba1d4c10e0684010", [:mix], [], "hexpm", "6db356b2bc6cc22561e051ff545c20ad064af57647e436650aa24d7d06cd941a"},
-   "certifi": {:hex, :certifi, "2.5.2", "b7cfeae9d2ed395695dd8201c57a2d019c0c43ecaf8b8bcb9320b40d6662f340", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "3b3b5f36493004ac3455966991eaf6e768ce9884693d9968055aeeeb1e575040"},
+   "castore": {:hex, :castore, "0.1.7", "1ca19eee705cde48c9e809e37fdd0730510752cc397745e550f6065a56a701e9", [:mix], [], "hexpm", "a2ae2c13d40e9c308387f1aceb14786dca019ebc2a11484fb2a9f797ea0aa0d8"},
+   "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"},
    "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
    "comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"},
+   "concurrent_limiter": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git", "d81be41024569330f296fc472e24198d7499ba78", [ref: "d81be41024569330f296fc472e24198d7499ba78"]},
    "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"},
-   "cors_plug": {:hex, :cors_plug, "1.5.2", "72df63c87e4f94112f458ce9d25800900cc88608c1078f0e4faddf20933eda6e", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9af027d20dc12dd0c4345a6b87247e0c62965871feea0bfecf9764648b02cc69"},
-   "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"},
-   "cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"},
-   "credo": {:hex, :credo, "1.1.5", "caec7a3cadd2e58609d7ee25b3931b129e739e070539ad1a0cd7efeeb47014f4", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d0bbd3222607ccaaac5c0340f7f525c627ae4d7aee6c8c8c108922620c5b6446"},
+   "cors_plug": {:hex, :cors_plug, "2.0.2", "2b46083af45e4bc79632bd951550509395935d3e7973275b2b743bd63cc942ce", [:mix], [{:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f0d0e13f71c51fd4ef8b2c7e051388e4dfb267522a83a22392c856de7e46465f"},
+   "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"},
+   "cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"},
+   "credo": {:hex, :credo, "1.4.0", "92339d4cbadd1e88b5ee43d427b639b68a11071b6f73854e33638e30a0ea11f5", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1fd3b70dce216574ce3c18bdf510b57e7c4c85c2ec9cad4bff854abaf7e58658"},
    "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
-   "crypt": {:git, "https://github.com/msantos/crypt", "f63a705f92c26955977ee62a313012e309a4d77a", [ref: "f63a705f92c26955977ee62a313012e309a4d77a"]},
+   "crypt": {:git, "https://github.com/msantos/crypt.git", "f63a705f92c26955977ee62a313012e309a4d77a", [ref: "f63a705f92c26955977ee62a313012e309a4d77a"]},
    "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
-   "db_connection": {:hex, :db_connection, "2.2.1", "caee17725495f5129cb7faebde001dc4406796f12a62b8949f4ac69315080566", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "2b02ece62d9f983fcd40954e443b7d9e6589664380e5546b2b9b523cd0fb59e1"},
+   "db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"},
    "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},
    "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
    "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"},
-   "ecto": {:hex, :ecto, "3.4.4", "a2c881e80dc756d648197ae0d936216c0308370332c5e77a2325a10293eef845", [: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", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4bd3ad62abc3b21fb629f0f7a3dab23a192fca837d257dd08449fba7373561"},
+   "earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"},
+   "ecto": {:hex, :ecto, "3.4.5", "2bcd262f57b2c888b0bd7f7a28c8a48aa11dc1a2c6a858e45dd8f8426d504265", [: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", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8c6d1d4d524559e9b7a062f0498e2c206122552d63eacff0a6567ffe7a8e8691"},
    "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_sql": {:hex, :ecto_sql, "3.3.4", "aa18af12eb875fbcda2f75e608b3bd534ebf020fc4f6448e4672fcdcbb081244", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4 or ~> 3.3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5eccbdbf92e3c6f213007a82d5dbba4cd9bb659d1a21331f89f408e4c0efd7a8"},
+   "ecto_sql": {:hex, :ecto_sql, "3.4.5", "30161f81b167d561a9a2df4329c10ae05ff36eca7ccc84628f2c8b9fa1e43323", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31990c6a3579b36a3c0841d34a94c275e727de8b84f58509da5f1b2032c98ac2"},
    "elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"},
    "esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"},
    "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"},
    "ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"},
-   "ex_aws": {:hex, :ex_aws, "2.1.1", "1e4de2106cfbf4e837de41be41cd15813eabc722315e388f0d6bb3732cec47cd", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "06b6fde12b33bb6d65d5d3493e903ba5a56d57a72350c15285a4298338089e10"},
+   "ex_aws": {:hex, :ex_aws, "2.1.3", "26b6f036f0127548706aade4a509978fc7c26bd5334b004fba9bfe2687a525df", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "0bdbe2aed9f326922fc5a6a80417e32f0c895f4b3b2b0b9676ebf23dd16c5da4"},
    "ex_aws_s3": {:hex, :ex_aws_s3, "2.0.2", "c0258bbdfea55de4f98f0b2f0ca61fe402cc696f573815134beb1866e778f47b", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "0569f5b211b1a3b12b705fe2a9d0e237eb1360b9d76298028df2346cad13097a"},
    "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"},
-   "ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "0db1ee8d1547ab4877c5b5dffc6604ef9454e189928d5ba8967d4a58a801f161"},
-   "ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "b84f6af156264530b312a8ab98ac6088f6b77ae5fe2058305c81434aa01fbaf9"},
+   "ex_doc": {:hex, :ex_doc, "0.22.2", "03a2a58bdd2ba0d83d004507c4ee113b9c521956938298eba16e55cc4aba4a6c", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "cf60e1b3e2efe317095b6bb79651f83a2c1b3edcb4d319c421d7fcda8b3aff26"},
+   "ex_machina": {:hex, :ex_machina, "2.4.0", "09a34c5d371bfb5f78399029194a8ff67aff340ebe8ba19040181af35315eabb", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "a20bc9ddc721b33ea913b93666c5d0bdca5cbad7a67540784ae277228832d72c"},
    "ex_syslogger": {:hex, :ex_syslogger, "1.5.2", "72b6aa2d47a236e999171f2e1ec18698740f40af0bd02c8c650bf5f1fd1bac79", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "ab9fab4136dbc62651ec6f16fa4842f10cf02ab4433fa3d0976c01be99398399"},
-   "excoveralls": {:hex, :excoveralls, "0.12.2", "a513defac45c59e310ac42fcf2b8ae96f1f85746410f30b1ff2b710a4b6cd44b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "151c476331d49b45601ffc45f43cb3a8beb396b02a34e3777fea0ad34ae57d89"},
-   "fast_html": {:hex, :fast_html, "1.0.3", "2cc0d4b68496266a1530e0c852cafeaede0bd10cfdee26fda50dc696c203162f", [:make, :mix], [], "hexpm", "ab3d782b639d3c4655fbaec0f9d032c91f8cab8dd791ac7469c2381bc7c32f85"},
-   "fast_sanitize": {:hex, :fast_sanitize, "0.1.7", "2a7cd8734c88a2de6de55022104f8a3b87f1fdbe8bbf131d9049764b53d50d0d", [:mix], [{:fast_html, "~> 1.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f39fe8ea08fbac17487c30bf09b7d9f3e12472e51fb07a88ffeb8fd17da8ab67"},
+   "excoveralls": {:hex, :excoveralls, "0.12.3", "2142be7cb978a3ae78385487edda6d1aff0e482ffc6123877bb7270a8ffbcfe0", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "568a3e616c264283f5dea5b020783ae40eef3f7ee2163f7a67cbd7b35bcadada"},
+   "fast_html": {:hex, :fast_html, "2.0.4", "4910ee49f2f6b19692e3bf30bf97f1b6b7dac489cd6b0f34cd0fe3042c56ba30", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "3bb49d541dfc02ad5e425904f53376d758c09f89e521afc7d2b174b3227761ea"},
+   "fast_sanitize": {:hex, :fast_sanitize, "0.2.2", "3cbbaebaea6043865dfb5b4ecb0f1af066ad410a51470e353714b10c42007b81", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "69f204db9250afa94a0d559d9110139850f57de2b081719fbafa1e9a89e94466"},
    "flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"},
-   "floki": {:hex, :floki, "0.25.0", "b1c9ddf5f32a3a90b43b76f3386ca054325dc2478af020e87b5111c19f2284ac", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "631f4e627c46d5ecd347df5a2accdaf0621c77c3693c5b75a8ad58e84c61f242"},
-   "gen_magic": {:git, "https://github.com/hrefhref/gen_magic", "eafdc2ea156433ccd87d1d99eaf1be758064a1db", [branch: "develop"]},
+   "floki": {:hex, :floki, "0.27.0", "6b29a14283f1e2e8fad824bc930eaa9477c462022075df6bea8f0ad811c13599", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "583b8c13697c37179f1f82443bcc7ad2f76fbc0bf4c186606eebd658f7f2631b"},
    "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"},
    "gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm"},
    "gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"},
-   "gettext": {:hex, :gettext, "0.17.4", "f13088e1ec10ce01665cf25f5ff779e7df3f2dc71b37084976cf89d1aa124d5c", [:mix], [], "hexpm", "3c75b5ea8288e2ee7ea503ff9e30dfe4d07ad3c054576a6e60040e79a801e14d"},
-   "gun": {:git, "https://github.com/ninenines/gun.git", "e1a69b36b180a574c0ac314ced9613fdd52312cc", [ref: "e1a69b36b180a574c0ac314ced9613fdd52312cc"]},
-   "hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"},
+   "gettext": {:hex, :gettext, "0.18.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"},
+   "gun": {:git, "https://github.com/ninenines/gun.git", "921c47146b2d9567eac7e9a4d2ccc60fffd4f327", [ref: "921c47146b2d9567eac7e9a4d2ccc60fffd4f327"]},
+   "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"},
    "html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"},
    "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
-   "http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "293d77bb6f4a67ac8bde1428735c3b42f22cbb30", [ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"]},
+   "http_signatures": {:hex, :http_signatures, "0.1.0", "4e4b501a936dbf4cb5222597038a89ea10781776770d2e185849fa829686b34c", [:mix], [], "hexpm", "f8a7b3731e3fd17d38fa6e343fcad7b03d6874a3b0a108c8568a71ed9c2cf824"},
    "httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"},
-   "idna": {:hex, :idna, "6.0.1", "1d038fb2e7668ce41fbf681d2c45902e52b3cb9e9c77b55334353b222c2ee50c", [:rebar3], [{:unicode_util_compat, "0.5.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a02c8a1c4fd601215bb0b0324c8a6986749f807ce35f25449ec9e69758708122"},
+   "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"},
    "inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"},
    "jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"},
    "joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "b4f92e30388206f869dd25d1af628a1d99d7586e5cf0672f64d4df84c4d2f5e9"},
    "jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"},
    "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
    "libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"},
-   "majic": {:git, "https://github.com/hrefhref/majic", "91a10cfa0c902eb889584238a3ef5aa595c21ef8", [branch: "develop"]},
-   "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a10c6eb62cca416019663129699769f0c2ccf39428b3bb3c0cb38c718a0c186d"},
-   "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"},
+   "linkify": {:hex, :linkify, "0.2.0", "2518bbbea21d2caa9d372424e1ad845b640c6630e2d016f1bd1f518f9ebcca28", [:mix], [], "hexpm", "b8ca8a68b79e30b7938d6c996085f3db14939f29538a59ca5101988bb7f917f6"},
++  "majic": {:git, "https://github.com/hrefhref/majic", "51ecea6458c4b2fbc98272ce5ab2951f2c0d46cc", [branch: "develop"]},
+   "makeup": {:hex, :makeup, "1.0.3", "e339e2f766d12e7260e6672dd4047405963c5ec99661abdc432e6ec67d29ef95", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "2e9b4996d11832947731f7608fed7ad2f9443011b3b479ae288011265cdd3dad"},
+   "makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"},
    "meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"},
    "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
-   "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"},
+   "mime": {:hex, :mime, "1.4.0", "5066f14944b470286146047d2f73518cf5cca82f8e4815cf35d196b58cf07c47", [:mix], [], "hexpm", "75fa42c4228ea9a23f70f123c74ba7cece6a03b1fd474fe13f6a7a85c6ea4ff6"},
    "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
    "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"},
-   "mock": {:hex, :mock, "0.3.4", "c5862eb3b8c64237f45f586cf00c9d892ba07bb48305a43319d428ce3c2897dd", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "e6d886252f1a41f4ba06ecf2b4c8d38760b34b1c08a11c28f7397b2e03995964"},
-   "mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm", "3bc928d817974fa10cc11e6c89b9a9361e37e96dbbf3d868c41094ec05745dcd"},
-   "mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm", "052346cf322311c49a0f22789f3698eea030eec09b8c47367f0686ef2634ae14"},
+   "mock": {:hex, :mock, "0.3.5", "feb81f52b8dcf0a0d65001d2fec459f6b6a8c22562d94a965862f6cc066b5431", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "6fae404799408300f863550392635d8f7e3da6b71abdd5c393faf41b131c8728"},
+   "mogrify": {:hex, :mogrify, "0.7.4", "9b2496dde44b1ce12676f85d7dc531900939e6367bc537c7243a1b089435b32d", [:mix], [], "hexpm", "50d79e337fba6bc95bfbef918058c90f50b17eed9537771e61d4619488f099c3"},
+   "mox": {:hex, :mox, "0.5.2", "55a0a5ba9ccc671518d068c8dddd20eeb436909ea79d1799e2209df7eaa98b6c", [:mix], [], "hexpm", "df4310628cd628ee181df93f50ddfd07be3e5ecc30232d3b6aadf30bdfe6092b"},
    "myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]},
-   "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"},
+   "nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"},
    "nimble_pool": {:hex, :nimble_pool, "0.1.0", "ffa9d5be27eee2b00b0c634eb649aa27f97b39186fec3c493716c2a33e784ec6", [:mix], [], "hexpm", "343a1eaa620ddcf3430a83f39f2af499fe2370390d4f785cd475b4df5acaf3f9"},
    "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
-   "oban": {:hex, :oban, "1.2.0", "7cca94d341be43d220571e28f69131c4afc21095b25257397f50973d3fc59b07", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ba5f8b3f7d76967b3e23cf8014f6a13e4ccb33431e4808f036709a7f822362ee"},
+   "oban": {:hex, :oban, "2.0.0", "e6ce70d94dd46815ec0882a1ffb7356df9a9d5b8a40a64ce5c2536617a447379", [:mix], [{:ecto_sql, ">= 3.4.3", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cf574813bd048b98a698aa587c21367d2e06842d4e1b1993dcd6a696e9e633bd"},
    "open_api_spex": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "f296ac0924ba3cf79c7a588c4c252889df4c2edd", [ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"]},
    "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
    "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"},
-   "phoenix": {:hex, :phoenix, "1.4.13", "67271ad69b51f3719354604f4a3f968f83aa61c19199343656c9caee057ff3b8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ab765a0feddb81fc62e2116c827b5f068df85159c162bee760745276ad7ddc1b"},
+   "phoenix": {:hex, :phoenix, "1.4.17", "1b1bd4cff7cfc87c94deaa7d60dd8c22e04368ab95499483c50640ef3bd838d8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a8e5d7a3d76d452bb5fb86e8b7bd115f737e4f8efe202a463d4aeb4a5809611"},
    "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"},
-   "phoenix_html": {:hex, :phoenix_html, "2.14.0", "d8c6bc28acc8e65f8ea0080ee05aa13d912c8758699283b8d3427b655aabe284", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b0bb30eda478a06dbfbe96728061a93833db3861a49ccb516f839ecb08493fbb"},
+   "phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"},
    "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm", "1f13f9f0f3e769a667a6b6828d29dec37497a082d195cc52dbef401a9b69bf38"},
-   "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.2.0", "a7e0b32077cd6d2323ae15198839b05d9caddfa20663fd85787479e81f89520e", [:mix], [{:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 0.1", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "ebf1bfa7b3c1c850c04929afe02e2e0d7ab135e0706332c865de03e761676b1f"},
-   "plug": {:hex, :plug, "1.9.0", "8d7c4e26962283ff9f8f3347bd73838e2413fbc38b7bb5467d5924f68f3a5a4a", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "9902eda2c52ada2a096434682e99a2493f5d06a94d6ac6bcfff9805f952350f1"},
-   "plug_cowboy": {:hex, :plug_cowboy, "2.1.2", "8b0addb5908c5238fac38e442e81b6fcd32788eaa03246b4d55d147c47c5805e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "7d722581ce865a237e14da6d946f92704101740a256bd13ec91e63c0b122fc70"},
+   "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.3.0", "2acfa0db038a7649e0a4614eee970e6ed9a39d191ccd79a03583b51d0da98165", [:mix], [{:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.0", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "b8bbae4b59a676de6b8bd8675eda37bc8b4424812ae429d6fdcb2b039e00003b"},
+   "plug": {:hex, :plug, "1.10.4", "41eba7d1a2d671faaf531fa867645bd5a3dce0957d8e2a3f398ccff7d2ef017f", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ad1e233fe73d2eec56616568d260777b67f53148a999dc2d048f4eb9778fe4a0"},
+   "plug_cowboy": {:hex, :plug_cowboy, "2.3.0", "149a50e05cb73c12aad6506a371cd75750c0b19a32f81866e1a323dda9e0e99d", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bc595a1870cef13f9c1e03df56d96804db7f702175e4ccacdb8fc75c02a7b97e"},
    "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"},
    "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.15.3", "5806baa8a19a68c4d07c7a624ccdb9b57e89cbc573f1b98099e3741214746ae4", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "4737ce62a31747b4c63c12b20c62307e51bb4fcd730ca0c32c280991e0606c90"},
-   "pot": {:hex, :pot, "0.10.2", "9895c83bcff8cd22d9f5bc79dfc88a188176b261b618ad70d93faf5c5ca36e67", [:rebar3], [], "hexpm", "ac589a8e296b7802681e93cd0a436faec117ea63e9916709c628df31e17e91e2"},
-   "prometheus": {:hex, :prometheus, "4.5.0", "8f4a2246fe0beb50af0f77c5e0a5bb78fe575c34a9655d7f8bc743aad1c6bf76", [:mix, :rebar3], [], "hexpm", "679b5215480fff612b8351f45c839d995a07ce403e42ff02f1c6b20960d41a4e"},
+   "postgrex": {:hex, :postgrex, "0.15.5", "aec40306a622d459b01bff890fa42f1430dac61593b122754144ad9033a2152f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ed90c81e1525f65a2ba2279dbcebf030d6d13328daa2f8088b9661eb9143af7f"},
+   "pot": {:hex, :pot, "0.11.0", "61bad869a94534739dd4614a25a619bc5c47b9970e9a0ea5bef4628036fc7a16", [:rebar3], [], "hexpm", "57ee6ee6bdeb639661ffafb9acefe3c8f966e45394de6a766813bb9e1be4e54b"},
+   "prometheus": {:hex, :prometheus, "4.6.0", "20510f381db1ccab818b4cf2fac5fa6ab5cc91bc364a154399901c001465f46f", [:mix, :rebar3], [], "hexpm", "4905fd2992f8038eccd7aa0cd22f40637ed618c0bed1f75c05aacec15b7545de"},
    "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8d66289f77f913b37eda81fd287340c17e61a447549deb28efc254532b2bed82"},
    "prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm", "9fd13404a48437e044b288b41f76e64acd9735fb8b0e3809f494811dfa66d0fb"},
    "prometheus_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "c4d1404ac4e9d3d963da601db2a7d8ea31194f0017057fabf0cfb9bf5a6c8c75"},
    "prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm", "0273a6483ccb936d79ca19b0ab629aef0dba958697c94782bb728b920dfc6a79"},
    "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.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
-   "recon": {:hex, :recon, "2.5.0", "2f7fcbec2c35034bade2f9717f77059dc54eb4e929a3049ca7ba6775c0bd66cd", [:mix, :rebar3], [], "hexpm", "72f3840fedd94f06315c523f6cecf5b4827233bed7ae3fe135b2a0ebeab5e196"},
+   "recon": {:hex, :recon, "2.5.1", "430ffa60685ac1efdfb1fe4c97b8767c92d0d92e6e7c3e8621559ba77598678a", [:mix, :rebar3], [], "hexpm", "5721c6b6d50122d8f68cccac712caa1231f97894bab779eff5ff0f886cb44648"},
    "remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]},
    "sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"},
-   "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
+   "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"},
    "sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"},
-   "swoosh": {:hex, :swoosh, "0.23.5", "bfd9404bbf5069b1be2ffd317923ce57e58b332e25dbca2a35dedd7820dfee5a", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "e3928e1d2889a308aaf3e42755809ac21cffd77cb58eef01cbfdab4ce2fd1e21"},
+   "swoosh": {:hex, :swoosh, "1.0.0", "c547cfc83f30e12d5d1fdcb623d7de2c2e29a5becfc68bf8f42ba4d23d2c2756", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "b3b08e463f876cb6167f7168e9ad99a069a724e124bcee61847e0e1ed13f4a0d"},
    "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
-   "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"},
-   "tesla": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/tesla.git", "61b7503cef33f00834f78ddfafe0d5d9dec2270b", [ref: "61b7503cef33f00834f78ddfafe0d5d9dec2270b"]},
-   "timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "f354efb2400dd7a80fd9eb6c8419068c4f632da4ac47f3d8822d6e33f08bc852"},
+   "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
+   "tesla": {:git, "https://github.com/teamon/tesla/", "9f7261ca49f9f901ceb73b60219ad6f8a9f6aa30", [ref: "9f7261ca49f9f901ceb73b60219ad6f8a9f6aa30"]},
+   "timex": {:hex, :timex, "3.6.2", "845cdeb6119e2fef10751c0b247b6c59d86d78554c83f78db612e3290f819bc2", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "26030b46199d02a590be61c2394b37ea25a3664c02fafbeca0b24c972025d47a"},
    "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
-   "tzdata": {:hex, :tzdata, "0.5.22", "f2ba9105117ee0360eae2eca389783ef7db36d533899b2e84559404dbc77ebb8", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "cd66c8a1e6a9e121d1f538b01bef459334bb4029a1ffb4eeeb5e4eae0337e7b6"},
-   "ueberauth": {:hex, :ueberauth, "0.6.2", "25a31111249d60bad8b65438b2306a4dc91f3208faa62f5a8c33e8713989b2e8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "db9fbfb5ac707bc4f85a297758406340bf0358b4af737a88113c1a9eee120ac7"},
-   "unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"},
+   "tzdata": {:hex, :tzdata, "1.0.3", "73470ad29dde46e350c60a66e6b360d3b99d2d18b74c4c349dbebbc27a09a3eb", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a6e1ee7003c4d04ecbd21dd3ec690d4c6662db5d3bbdd7262d53cdf5e7c746c1"},
+   "ueberauth": {:hex, :ueberauth, "0.6.3", "d42ace28b870e8072cf30e32e385579c57b9cc96ec74fa1f30f30da9c14f3cc0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "afc293d8a1140d6591b53e3eaf415ca92842cb1d32fad3c450c6f045f7f91b60"},
+   "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"},
    "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},
-   "web_push_encryption": {:hex, :web_push_encryption, "0.2.3", "a0ceab85a805a30852f143d22d71c434046fbdbafbc7292e7887cec500826a80", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "9315c8f37c108835cf3f8e9157d7a9b8f420a34f402d1b1620a31aed5b93ecdf"},
+   "web_push_encryption": {:hex, :web_push_encryption, "0.3.0", "598b5135e696fd1404dc8d0d7c0fa2c027244a4e5d5e5a98ba267f14fdeaabc8", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "f10bdd1afe527ede694749fb77a2f22f146a51b054c7fa541c9fd920fba7c875"},
    "websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []},
  }
diff --combined test/upload_test.exs
index c7ad177d9ef464f4c1b195d0a3d7d3b6957fad67,b06b54487e152dc397e3127930e481cefbaedace..4280bfcacca7147e4b05d3ef551c448346d8ebdc
@@@ -11,7 -11,7 +11,7 @@@ defmodule Pleroma.UploadTest d
    alias Pleroma.Uploaders.Uploader
  
    @upload_file %Plug.Upload{
 -    content_type: "image/jpg",
 +    content_type: "image/jpeg",
      path: Path.absname("test/fixtures/image_tmp.jpg"),
      filename: "image.jpg"
    }
    describe "Storing a file with the Local uploader" do
      setup [:ensure_local_uploader]
  
+     test "does not allow descriptions longer than the post limit" do
+       clear_config([:instance, :description_limit], 2)
+       File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
+       file = %Plug.Upload{
+         content_type: "image/jpg",
+         path: Path.absname("test/fixtures/image_tmp.jpg"),
+         filename: "image.jpg"
+       }
+       {:error, :description_too_long} = Upload.store(file, description: "123")
+     end
      test "returns a media url" do
        File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
  
        file = %Plug.Upload{
 -        content_type: "image/jpg",
 +        content_type: "image/jpeg",
          path: Path.absname("test/fixtures/image_tmp.jpg"),
          filename: "image.jpg"
        }
        File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
  
        file = %Plug.Upload{
 -        content_type: "image/jpg",
 +        content_type: "image/jpeg",
          path: Path.absname("test/fixtures/image_tmp.jpg"),
          filename: "an [image.jpg"
        }
        File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
  
        file = %Plug.Upload{
 -        content_type: "image/jpg",
 +        content_type: "image/jpeg",
          path: Path.absname("test/fixtures/image_tmp.jpg"),
          filename: "an [image.jpg"
        }
        assert data["name"] == "an [image.jpg"
      end
  
 -    test "fixes incorrect content type" do
 -      File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
 -
 -      file = %Plug.Upload{
 -        content_type: "application/octet-stream",
 -        path: Path.absname("test/fixtures/image_tmp.jpg"),
 -        filename: "an [image.jpg"
 +    test "fixes incorrect content type when base64 is given" do
 +      params = %{
 +        img: "data:image/png;base64,#{Base.encode64(File.read!("test/fixtures/image.jpg"))}"
        }
  
 -      {:ok, data} = Upload.store(file, filters: [Pleroma.Upload.Filter.Dedupe])
 +      {:ok, data} = Upload.store(params)
        assert hd(data["url"])["mediaType"] == "image/jpeg"
      end
  
 -    test "adds missing extension" do
 -      File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
 -
 -      file = %Plug.Upload{
 -        content_type: "image/jpg",
 -        path: Path.absname("test/fixtures/image_tmp.jpg"),
 -        filename: "an [image"
 -      }
 -
 -      {:ok, data} = Upload.store(file)
 -      assert data["name"] == "an [image.jpg"
 -    end
 -
 -    test "fixes incorrect file extension" do
 +    test "adds extension when base64 is given" do
        File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
  
 -      file = %Plug.Upload{
 -        content_type: "image/jpg",
 -        path: Path.absname("test/fixtures/image_tmp.jpg"),
 -        filename: "an [image.blah"
 -      }
 -
 -      {:ok, data} = Upload.store(file)
 -      assert data["name"] == "an [image.jpg"
 -    end
 -
 -    test "don't modify filename of an unknown type" do
 -      File.cp("test/fixtures/test.txt", "test/fixtures/test_tmp.txt")
 -
 -      file = %Plug.Upload{
 -        content_type: "text/plain",
 -        path: Path.absname("test/fixtures/test_tmp.txt"),
 -        filename: "test.txt"
 +      params = %{
 +        img: "data:image/png;base64,#{Base.encode64(File.read!("test/fixtures/image.jpg"))}"
        }
  
 -      {:ok, data} = Upload.store(file)
 -      assert data["name"] == "test.txt"
 +      {:ok, data} = Upload.store(params)
 +      assert String.ends_with?(data["name"], ".jpg")
      end
  
      test "copies the file to the configured folder with anonymizing filename" do
        File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
  
        file = %Plug.Upload{
 -        content_type: "image/jpg",
 +        content_type: "image/jpeg",
          path: Path.absname("test/fixtures/image_tmp.jpg"),
          filename: "an [image.jpg"
        }
        File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
  
        file = %Plug.Upload{
 -        content_type: "image/jpg",
 +        content_type: "image/jpeg",
          path: Path.absname("test/fixtures/image_tmp.jpg"),
          filename: "an… image.jpg"
        }
        File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
  
        file = %Plug.Upload{
 -        content_type: "image/jpg",
 +        content_type: "image/jpeg",
          path: Path.absname("test/fixtures/image_tmp.jpg"),
          filename: ":?#[]@!$&\\'()*+,;=.jpg"
        }
        File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
  
        file = %Plug.Upload{
 -        content_type: "image/jpg",
 +        content_type: "image/jpeg",
          path: Path.absname("test/fixtures/image_tmp.jpg"),
          filename: "image.jpg"
        }
index 8c6ee68b2bf920e4271e567db529a0af1ff062d9,0517571f28f7cde9cbfa7bc36cb9fe274da313e9..b11e2f9619defd0fe2fd42f4af3aaa000a3f9613
@@@ -533,9 -533,10 +533,10 @@@ defmodule Pleroma.Web.ActivityPub.Activ
        end)
  
        :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
-       assert_receive {:mix_shell, :info, ["relay.mastodon.host"]}
+       assert_receive {:mix_shell, :info, ["https://relay.mastodon.host/actor"]}
      end
  
+     @tag capture_log: true
      test "without valid signature, " <>
             "it only accepts Create activities and requires enabled federation",
           %{conn: conn} do
      test "it accepts announces with to as string instead of array", %{conn: conn} do
        user = insert(:user)
  
+       {:ok, post} = CommonAPI.post(user, %{status: "hey"})
+       announcer = insert(:user, local: false)
        data = %{
          "@context" => "https://www.w3.org/ns/activitystreams",
-         "actor" => "http://mastodon.example.org/users/admin",
-         "id" => "http://mastodon.example.org/users/admin/statuses/19512778738411822/activity",
-         "object" => "https://mastodon.social/users/emelie/statuses/101849165031453009",
+         "actor" => announcer.ap_id,
+         "id" => "#{announcer.ap_id}/statuses/19512778738411822/activity",
+         "object" => post.data["object"],
          "to" => "https://www.w3.org/ns/activitystreams#Public",
          "cc" => [user.ap_id],
          "type" => "Announce"
    end
  
    describe "POST /users/:nickname/outbox (C2S)" do
+     setup do: clear_config([:instance, :limit])
      setup do
        [
          activity: %{
        assert object = Object.get_by_ap_id(note_object.data["id"])
        assert object.data["like_count"] == 1
      end
+     test "it doesn't spreads faulty attributedTo or actor fields", %{
+       conn: conn,
+       activity: activity
+     } do
+       reimu = insert(:user, nickname: "reimu")
+       cirno = insert(:user, nickname: "cirno")
+       assert reimu.ap_id
+       assert cirno.ap_id
+       activity =
+         activity
+         |> put_in(["object", "actor"], reimu.ap_id)
+         |> put_in(["object", "attributedTo"], reimu.ap_id)
+         |> put_in(["actor"], reimu.ap_id)
+         |> put_in(["attributedTo"], reimu.ap_id)
+       _reimu_outbox =
+         conn
+         |> assign(:user, cirno)
+         |> put_req_header("content-type", "application/activity+json")
+         |> post("/users/#{reimu.nickname}/outbox", activity)
+         |> json_response(403)
+       cirno_outbox =
+         conn
+         |> assign(:user, cirno)
+         |> put_req_header("content-type", "application/activity+json")
+         |> post("/users/#{cirno.nickname}/outbox", activity)
+         |> json_response(201)
+       assert cirno_outbox["attributedTo"] == nil
+       assert cirno_outbox["actor"] == cirno.ap_id
+       assert cirno_object = Object.normalize(cirno_outbox["object"])
+       assert cirno_object.data["actor"] == cirno.ap_id
+       assert cirno_object.data["attributedTo"] == cirno.ap_id
+     end
+     test "Character limitation", %{conn: conn, activity: activity} do
+       Pleroma.Config.put([:instance, :limit], 5)
+       user = insert(:user)
+       result =
+         conn
+         |> assign(:user, user)
+         |> put_req_header("content-type", "application/activity+json")
+         |> post("/users/#{user.nickname}/outbox", activity)
+         |> json_response(400)
+       assert result == "Note is over the character limit"
+     end
    end
  
    describe "/relay/followers" do
        desc = "Description of the image"
  
        image = %Plug.Upload{
 -        content_type: "image/jpg",
 +        content_type: "bad/content-type",
          path: Path.absname("test/fixtures/image.jpg"),
 -        filename: "an_image.jpg"
 +        filename: "an_image.png"
        }
  
        object =
        assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
        assert is_binary(object_href)
        assert object_mediatype == "image/jpeg"
 +      assert String.ends_with?(object_href, ".jpg")
  
        activity_request = %{
          "@context" => "https://www.w3.org/ns/activitystreams",