From 02edd04ccaa983705725b95da4d3edfab5f6a5b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Sun, 7 Sep 2025 23:28:58 +0200 Subject: [PATCH 01/40] Add v1/instance/domain_blocks endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- changelog.d/instance-domain-blocks.add | 1 + .../api_spec/operations/instance_operation.ex | 31 ++++++++++++ .../controllers/instance_controller.ex | 7 ++- .../web/mastodon_api/views/instance_view.ex | 49 +++++++++++++++++++ lib/pleroma/web/router.ex | 1 + .../controllers/instance_controller_test.exs | 27 ++++++++++ 6 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 changelog.d/instance-domain-blocks.add diff --git a/changelog.d/instance-domain-blocks.add b/changelog.d/instance-domain-blocks.add new file mode 100644 index 000000000..85f01c5c2 --- /dev/null +++ b/changelog.d/instance-domain-blocks.add @@ -0,0 +1 @@ +Add v1/instance/domain_blocks endpoint diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex index 911ffb994..d8b2901d3 100644 --- a/lib/pleroma/web/api_spec/operations/instance_operation.ex +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -57,6 +57,22 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do } end + def domain_blocks_operation do + %Operation{ + tags: ["Instance misc"], + summary: "Retrieve instance domain blocks", + operationId: "InstanceController.domain_blocks", + responses: %{ + 200 => + Operation.response( + "Array of domain blocks", + "application/json", + array_of_domain_blocks() + ) + } + } + end + def translation_languages_operation do %Operation{ tags: ["Instance misc"], @@ -420,4 +436,19 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do } } end + + defp array_of_domain_blocks do + %Schema{ + type: :array, + items: %Schema{ + type: :object, + properties: %{ + domain: %Schema{type: :string}, + digest: %Schema{type: :string}, + severity: %Schema{type: :string}, + comment: %Schema{type: :string} + } + } + } + end end diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex index 0f74c1dff..cf5bbf16e 100644 --- a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do plug(Pleroma.Web.ApiSpec.CastAndValidate) - plug(:skip_auth when action in [:show, :show2, :peers]) + plug(:skip_auth) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.InstanceOperation @@ -31,6 +31,11 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do render(conn, "rules.json") end + @doc "GET /api/v1/instance/domain_blocks" + def domain_blocks(conn, _params) do + render(conn, "domain_blocks.json") + end + @doc "GET /api/v1/instance/translation_languages" def translation_languages(conn, _params) do render(conn, "translation_languages.json") diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index b21383659..75a391bd0 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -5,11 +5,18 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do use Pleroma.Web, :view + import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1] + alias Pleroma.Config alias Pleroma.Web.ActivityPub.MRF @mastodon_api_level "2.7.2" + @block_severities %{ + federated_timeline_removal: "silence", + reject: "suspend" + } + def render("show.json", _) do instance = Config.get(:instance) @@ -90,6 +97,48 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do } end + def render("domain_blocks.json", _) do + if Config.get([:mrf, :transparency]) do + exclusions = Config.get([:mrf, :transparency_exclusions]) |> MRF.instance_list_from_tuples() + + domain_blocks = + Config.get(:mrf_simple) + |> Enum.map(fn {rule, instances} -> + instances + |> Enum.map(fn + {host, reason} when not_empty_string(host) and not_empty_string(reason) -> + {host, reason} + + {host, _reason} when not_empty_string(host) -> + {host, ""} + + host when not_empty_string(host) -> + {host, ""} + + _ -> + nil + end) + |> Enum.reject(&is_nil/1) + |> Enum.reject(fn {host, _} -> + host in exclusions or not Map.has_key?(@block_severities, rule) + end) + |> Enum.map(fn {host, reason} -> + %{ + domain: host, + digest: :crypto.hash(:sha256, host) |> Base.encode16(case: :lower), + severity: Map.get(@block_severities, rule), + comment: reason + } + end) + end) + |> List.flatten() + + domain_blocks + else + [] + end + end + def render("translation_languages.json", _) do with true <- Pleroma.Language.Translation.configured?(), {:ok, languages} <- Pleroma.Language.Translation.languages_matrix() do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 5e1ef7480..85b6b986d 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -798,6 +798,7 @@ defmodule Pleroma.Web.Router do get("/instance", InstanceController, :show) get("/instance/peers", InstanceController, :peers) get("/instance/rules", InstanceController, :rules) + get("/instance/domain_blocks", InstanceController, :domain_blocks) get("/instance/translation_languages", InstanceController, :translation_languages) get("/statuses", StatusController, :index) diff --git a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs index 10c0b6ea7..e0c9091e0 100644 --- a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs @@ -153,6 +153,33 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do ] = result["rules"] end + describe "instance domain blocks" do + setup do + clear_config([:mrf_simple, :reject], [{"fediverse.pl", "uses pl-fe"}]) + end + + test "get instance domain blocks", %{conn: conn} do + conn = get(conn, "/api/v1/instance/domain_blocks") + + assert [ + %{ + "comment" => "uses pl-fe", + "digest" => "55e3f44aefe7eb022d3b1daaf7396cabf7f181bf6093c8ea841e30c9fc7d8226", + "domain" => "fediverse.pl", + "severity" => "suspend" + } + ] == json_response_and_validate_schema(conn, 200) + end + + test "returns empty array if mrf transparency is disabled", %{conn: conn} do + clear_config([:mrf, :transparency], false) + + conn = get(conn, "/api/v1/instance/domain_blocks") + + assert [] == json_response_and_validate_schema(conn, 200) + end + end + test "translation languages matrix", %{conn: conn} do clear_config([Pleroma.Language.Translation, :provider], TranslationMock) From e091349718f7458775d92782135f4b32c883062c Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 26 Dec 2025 05:08:52 +0000 Subject: [PATCH 02/40] Revert "Merge branch 'revert-cdd6df06' into 'develop'" This reverts merge request !4411 --- changelog.d/hackney.change | 1 + mix.exs | 2 +- mix.lock | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 changelog.d/hackney.change diff --git a/changelog.d/hackney.change b/changelog.d/hackney.change new file mode 100644 index 000000000..3158cfc77 --- /dev/null +++ b/changelog.d/hackney.change @@ -0,0 +1 @@ +Update Hackney, the default HTTP client, to the latest release which supports Happy Eyeballs for improved IPv6 federation diff --git a/mix.exs b/mix.exs index 546b60005..8e486ba13 100644 --- a/mix.exs +++ b/mix.exs @@ -210,7 +210,7 @@ defmodule Pleroma.Mixfile do {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, {:mock, "~> 0.3.5", only: :test}, {:covertool, "~> 2.0", only: :test}, - {:hackney, "~> 1.18.0", override: true}, + {:hackney, "~> 1.25.0", override: true}, {:mox, "~> 1.0", only: :test}, {:websockex, "~> 0.4.3", only: :test}, {:benchee, "~> 1.0", only: :benchmark}, diff --git a/mix.lock b/mix.lock index 3599f62b4..b8c9e240d 100644 --- a/mix.lock +++ b/mix.lock @@ -13,7 +13,7 @@ "captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "e7b7cc34cc16b383461b966484c297e4ec9aeef6", [ref: "e7b7cc34cc16b383461b966484c297e4ec9aeef6"]}, "castore": {:hex, :castore, "1.0.15", "8aa930c890fe18b6fe0a0cff27b27d0d4d231867897bd23ea772dee561f032a3", [:mix], [], "hexpm", "96ce4c69d7d5d7a0761420ef743e2f4096253931a3ba69e5ff8ef1844fe446d3"}, "cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"}, - "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, + "certifi": {:hex, :certifi, "2.15.0", "0e6e882fcdaaa0a5a9f2b3db55b1394dba07e8d6d9bcad08318fb604c6839712", [:rebar3], [], "hexpm", "b147ed22ce71d72eafdad94f055165c1c182f61a2ff49df28bcc71d1d5b94a60"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "comeonin": {:hex, :comeonin, "5.5.1", "5113e5f3800799787de08a6e0db307133850e635d34e9fab23c70b6501669510", [:mix], [], "hexpm", "65aac8f19938145377cee73973f192c5645873dcf550a8a6b18187d17c13ccdb"}, "concurrent_limiter": {:hex, :concurrent_limiter, "0.1.1", "43ae1dc23edda1ab03dd66febc739c4ff710d047bb4d735754909f9a474ae01c", [:mix], [{:telemetry, "~> 0.3", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "53968ff238c0fbb4d7ed76ddb1af0be6f3b2f77909f6796e249e737c505a16eb"}, @@ -59,7 +59,7 @@ "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"}, "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, "gun": {:hex, :gun, "2.2.0", "b8f6b7d417e277d4c2b0dc3c07dfdf892447b087f1cc1caff9c0f556b884e33d", [:make, :rebar3], [{:cowlib, ">= 2.15.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "76022700c64287feb4df93a1795cff6741b83fb37415c40c34c38d2a4645261a"}, - "hackney": {:hex, :hackney, "1.18.2", "d7ff544ddae5e1cb49e9cf7fa4e356d7f41b283989a1c304bfc47a8cc1cf966f", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "af94d5c9f97857db257090a4a10e5426ecb6f4918aa5cc666798566ae14b65fd"}, + "hackney": {:hex, :hackney, "1.25.0", "390e9b83f31e5b325b9f43b76e1a785cbdb69b5b6cd4e079aa67835ded046867", [:rebar3], [{:certifi, "~> 2.15.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.4", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "7209bfd75fd1f42467211ff8f59ea74d6f2a9e81cbcee95a56711ee79fd6b1d4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, "http_signatures": {:hex, :http_signatures, "0.1.2", "ed1cc7043abcf5bb4f30d68fb7bad9d618ec1a45c4ff6c023664e78b67d9c406", [:mix], [], "hexpm", "f08aa9ac121829dae109d608d83c84b940ef2f183ae50f2dd1e9a8bc619d8be7"}, From db48aa5cdb31772d738f31cfcd591c2d35ec5551 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Wed, 31 Dec 2025 18:52:14 +0400 Subject: [PATCH 03/40] Installation: Add Release-Via-Docker option --- docs/installation/otp_en.md | 3 + docs/installation/release_to_docker_en.md | 61 +++++++++++++++++ installation/release-to-docker/Dockerfile | 24 +++++++ installation/release-to-docker/README.md | 66 +++++++++++++++++++ .../release-to-docker/docker-compose.yml | 22 +++++++ .../pleroma-host-release-entrypoint.sh | 19 ++++++ .../release-to-docker/pleroma.service | 16 +++++ 7 files changed, 211 insertions(+) create mode 100644 docs/installation/release_to_docker_en.md create mode 100644 installation/release-to-docker/Dockerfile create mode 100644 installation/release-to-docker/README.md create mode 100644 installation/release-to-docker/docker-compose.yml create mode 100644 installation/release-to-docker/pleroma-host-release-entrypoint.sh create mode 100644 installation/release-to-docker/pleroma.service diff --git a/docs/installation/otp_en.md b/docs/installation/otp_en.md index 86efa27f8..123dccf3a 100644 --- a/docs/installation/otp_en.md +++ b/docs/installation/otp_en.md @@ -13,6 +13,9 @@ You will be running commands as root. If you aren't root already, please elevate Similarly to other binaries, OTP releases tend to be only compatible with the distro they are built on, as such this guide focuses only on Debian/Ubuntu and Alpine. +!!! note + If you get `GLIBC_... not found` errors on Debian/Ubuntu, you can run the OTP release from `/opt/pleroma` inside a newer distro container without upgrading the host. See [`release_to_docker_en.md`](release_to_docker_en.md). + ### Detecting flavour Paste the following into the shell: diff --git a/docs/installation/release_to_docker_en.md b/docs/installation/release_to_docker_en.md new file mode 100644 index 000000000..a1fd165ac --- /dev/null +++ b/docs/installation/release_to_docker_en.md @@ -0,0 +1,61 @@ +# Running OTP releases via Docker (glibc shim) + +Pleroma OTP releases are built on specific distros. If your host OS is older than +the build environment, you may hit runtime linker errors such as: + +``` +/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.38' not found +``` + +If you don't want to upgrade your host OS, you can run the existing OTP release +from `/opt/pleroma` inside an Ubuntu 24.04 container while keeping your existing +host config and data directories. + +This approach uses a small "shim" container image to provide a newer `glibc`. +It is **not** the official Pleroma Docker image. + +## Requirements + +- Docker Engine + the Docker Compose plugin on the host +- Root access (or equivalent access to the Docker socket) +- Existing OTP release in `/opt/pleroma` +- Existing config in `/etc/pleroma` and data in `/var/lib/pleroma` + +## Setup + +1. Copy the provided templates: + + ```sh + mkdir -p /etc/pleroma/container + cp -a /opt/pleroma/installation/release-to-docker/* /etc/pleroma/container/ + ``` + +2. Build the shim image: + + ```sh + cd /etc/pleroma/container + docker compose build + ``` + +3. Replace your systemd unit: + + ```sh + cp /etc/pleroma/container/pleroma.service /etc/systemd/system/pleroma.service + systemctl daemon-reload + systemctl enable --now pleroma + journalctl -u pleroma -f + ``` + +## Running migrations / `pleroma_ctl` + +Migrations are run automatically by default when the container starts. You can +disable this by setting `PLEROMA_RUN_MIGRATIONS=0` in +`/etc/pleroma/container/docker-compose.yml`. + +To run admin commands inside the container: + +```sh +cd /etc/pleroma/container +docker compose exec pleroma /opt/pleroma/bin/pleroma_ctl status +docker compose run --rm --no-deps pleroma /opt/pleroma/bin/pleroma_ctl migrate +``` diff --git a/installation/release-to-docker/Dockerfile b/installation/release-to-docker/Dockerfile new file mode 100644 index 000000000..ddff4570d --- /dev/null +++ b/installation/release-to-docker/Dockerfile @@ -0,0 +1,24 @@ +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive \ + LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + gosu \ + libstdc++6 \ + libncurses6 libncursesw6 \ + openssl libssl3 \ + libmagic1t64 file \ + postgresql-client \ + ffmpeg imagemagick libimage-exiftool-perl \ + libvips42t64 \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /opt/pleroma + +COPY pleroma-host-release-entrypoint.sh /usr/local/bin/pleroma-host-release-entrypoint.sh +RUN chmod +x /usr/local/bin/pleroma-host-release-entrypoint.sh +ENTRYPOINT ["/usr/local/bin/pleroma-host-release-entrypoint.sh"] +CMD ["/opt/pleroma/bin/pleroma", "start"] diff --git a/installation/release-to-docker/README.md b/installation/release-to-docker/README.md new file mode 100644 index 000000000..4129bd94b --- /dev/null +++ b/installation/release-to-docker/README.md @@ -0,0 +1,66 @@ +# Run OTP releases on older glibc using Docker + +Pleroma OTP releases are built on specific distros and may require a newer `glibc` +than your host has. A typical failure looks like: + +``` +... /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.38' not found ... +``` + +If you don't want to upgrade your host OS, you can run the existing OTP release +from `/opt/pleroma` inside an Ubuntu 24.04 container while keeping your existing +host paths (`/etc/pleroma`, `/var/lib/pleroma`, etc.). + +This folder provides a "shim" container + systemd unit. It is **not** the Pleroma +Docker image. + +## What this does + +- Builds a small Ubuntu 24.04 image with runtime libs (including newer `glibc`). +- Mounts your existing host release at `/opt/pleroma` into the container. +- Runs as the same UID/GID that owns `/opt/pleroma` on the host (via `gosu`). +- Optionally runs migrations automatically on container start. +- Uses `network_mode: host` so your existing config that talks to `localhost` + keeps working. + +## Setup (Debian/Ubuntu host) + +1. Install Docker Engine + the Docker Compose plugin. +2. Copy these files to a stable location (example: `/etc/pleroma/container`): + + ``` + mkdir -p /etc/pleroma/container + cp -a /opt/pleroma/installation/release-to-docker/* /etc/pleroma/container/ + ``` + +3. Build the shim image: + + ``` + cd /etc/pleroma/container + docker compose build + ``` + +4. Replace your systemd unit: + + ``` + cp /etc/pleroma/container/pleroma.service /etc/systemd/system/pleroma.service + systemctl daemon-reload + systemctl enable --now pleroma + journalctl -u pleroma -f + ``` + +## Running `pleroma_ctl` + +Since the host binary may not run on older `glibc`, run admin commands inside the +container: + +``` +cd /etc/pleroma/container +docker compose exec pleroma /opt/pleroma/bin/pleroma_ctl status +docker compose run --rm --no-deps pleroma /opt/pleroma/bin/pleroma_ctl migrate +``` + +## Configuration notes + +- Migrations run automatically by default. + - Set `PLEROMA_RUN_MIGRATIONS=0` in `docker-compose.yml` to disable. diff --git a/installation/release-to-docker/docker-compose.yml b/installation/release-to-docker/docker-compose.yml new file mode 100644 index 000000000..043803241 --- /dev/null +++ b/installation/release-to-docker/docker-compose.yml @@ -0,0 +1,22 @@ +services: + pleroma: + build: + context: . + dockerfile: Dockerfile + image: pleroma-host-release-wrapper:ubuntu24 + init: true + network_mode: host + restart: unless-stopped + environment: + HOME: /opt/pleroma + LANG: C.UTF-8 + LC_ALL: C.UTF-8 + ELIXIR_ERL_OPTIONS: "+fnu" + # Set to 0 to skip running migrations on container start. + PLEROMA_RUN_MIGRATIONS: "1" + volumes: + # Existing OTP release installation (host) + - /opt/pleroma:/opt/pleroma:rw + # Existing config + uploads + static (host) + - /etc/pleroma:/etc/pleroma:rw + - /var/lib/pleroma:/var/lib/pleroma:rw diff --git a/installation/release-to-docker/pleroma-host-release-entrypoint.sh b/installation/release-to-docker/pleroma-host-release-entrypoint.sh new file mode 100644 index 000000000..b9c035678 --- /dev/null +++ b/installation/release-to-docker/pleroma-host-release-entrypoint.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +uid="${PLEROMA_UID:-}" +gid="${PLEROMA_GID:-}" + +if [[ -z "${uid}" || -z "${gid}" ]]; then + uid="$(stat -c '%u' /opt/pleroma)" + gid="$(stat -c '%g' /opt/pleroma)" +fi + +export HOME="${HOME:-/opt/pleroma}" + +if [[ "${PLEROMA_RUN_MIGRATIONS:-1}" != "0" && "${1:-}" == "/opt/pleroma/bin/pleroma" && "${2:-}" == "start" ]]; then + echo "Running migrations..." + gosu "${uid}:${gid}" /opt/pleroma/bin/pleroma_ctl migrate +fi + +exec gosu "${uid}:${gid}" "$@" diff --git a/installation/release-to-docker/pleroma.service b/installation/release-to-docker/pleroma.service new file mode 100644 index 000000000..d3833363c --- /dev/null +++ b/installation/release-to-docker/pleroma.service @@ -0,0 +1,16 @@ +[Unit] +Description=Pleroma social network (OTP release via Docker glibc shim) +After=network.target docker.service postgresql.service nginx.service +Requires=docker.service + +[Service] +Type=simple +WorkingDirectory=/etc/pleroma/container +ExecStart=/usr/bin/docker compose up --build --remove-orphans +ExecStop=/usr/bin/docker compose down +Restart=on-failure +RestartSec=5s +TimeoutStartSec=0 + +[Install] +WantedBy=multi-user.target From e3bdb8ef5df7de608d69bb0422eeb81aa4d8ffb2 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Thu, 1 Jan 2026 09:00:53 +0400 Subject: [PATCH 04/40] Release-to-Docker: Add unzip / curl to make updates work --- installation/release-to-docker/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/installation/release-to-docker/Dockerfile b/installation/release-to-docker/Dockerfile index ddff4570d..0ffeb5d5b 100644 --- a/installation/release-to-docker/Dockerfile +++ b/installation/release-to-docker/Dockerfile @@ -14,6 +14,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ postgresql-client \ ffmpeg imagemagick libimage-exiftool-perl \ libvips42t64 \ + unzip \ + curl \ && rm -rf /var/lib/apt/lists/* WORKDIR /opt/pleroma From 009e63d20b880b0f7bc27a8ced9f0bf55d84f0d7 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Thu, 1 Jan 2026 10:39:38 +0400 Subject: [PATCH 05/40] Add changelog --- changelog.d/release-to-docker.add | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/release-to-docker.add diff --git a/changelog.d/release-to-docker.add b/changelog.d/release-to-docker.add new file mode 100644 index 000000000..5fbf611a5 --- /dev/null +++ b/changelog.d/release-to-docker.add @@ -0,0 +1 @@ +Add instructions on how to run a release in docker, to make it easier to run on older distros. From 033618b25e9073c531bd09e2deafff0600cfcf3e Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Mon, 5 Jan 2026 11:57:02 +0400 Subject: [PATCH 06/40] TransmogrifierTest: Add failing test for EmojiReact url encoding --- .../web/activity_pub/transmogrifier_test.exs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs index 6dc4423fa..b54196a3c 100644 --- a/test/pleroma/web/activity_pub/transmogrifier_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs @@ -759,6 +759,22 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do ) end + test "EmojiReact custom emoji urls are URI encoded" do + user = insert(:user, local: true) + note_activity = insert(:note_activity) + + {:ok, react_activity} = CommonAPI.react_with_emoji(note_activity.id, user, ":dinosaur:") + {:ok, data} = Transmogrifier.prepare_outgoing(react_activity.data) + + assert length(data["tag"]) == 1 + + tag = List.first(data["tag"]) + url = tag["icon"]["url"] + + assert url == "http://localhost:4001/emoji/dino%20walking.gif" + assert tag["id"] == "http://localhost:4001/emoji/dino%20walking.gif" + end + test "it prepares a quote post" do user = insert(:user) From bac607c7c042d1c85037a0452967b4a0b231d679 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Mon, 5 Jan 2026 11:57:38 +0400 Subject: [PATCH 07/40] Emoji: Unify tag building, fix tests. --- lib/pleroma/emoji.ex | 12 ++++++++++++ lib/pleroma/web/activity_pub/builder.ex | 10 +--------- lib/pleroma/web/activity_pub/transmogrifier.ex | 17 +++-------------- .../transmogrifier/emoji_tag_building_test.exs | 2 +- .../views/notification_view_test.exs | 4 ++-- .../web/mastodon_api/views/status_view_test.exs | 4 ++-- .../emoji_reaction_controller_test.exs | 2 +- 7 files changed, 22 insertions(+), 29 deletions(-) diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index 21bcb0111..8c0257ef4 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -189,6 +189,18 @@ defmodule Pleroma.Emoji do def emoji_url(_), do: nil + def build_emoji_tag({name, url}) do + url = URI.encode(url) + + %{ + "icon" => %{"url" => "#{url}", "type" => "Image"}, + "name" => ":" <> name <> ":", + "type" => "Emoji", + "updated" => "1970-01-01T00:00:00Z", + "id" => url + } + end + def emoji_name_with_instance(name, url) do url = url |> URI.parse() |> Map.get(:host) "#{name}@#{url}" diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 046316024..4146fd6ab 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -64,15 +64,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do defp add_emoji_content(data, emoji, url) do tag = [ - %{ - "id" => url, - "type" => "Emoji", - "name" => Emoji.maybe_quote(emoji), - "icon" => %{ - "type" => "Image", - "url" => url - } - } + Emoji.build_emoji_tag({Emoji.maybe_strip_name(emoji), url}) ] data diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 6e04d95e6..a4b1ae349 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do """ @behaviour Pleroma.Web.ActivityPub.Transmogrifier.API alias Pleroma.Activity + alias Pleroma.Emoji alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.Maps alias Pleroma.Object @@ -1005,32 +1006,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def take_emoji_tags(%User{emoji: emoji}) do emoji |> Map.to_list() - |> Enum.map(&build_emoji_tag/1) + |> Enum.map(&Emoji.build_emoji_tag/1) end # TODO: we should probably send mtime instead of unix epoch time for updated def add_emoji_tags(%{"emoji" => emoji} = object) do tags = object["tag"] || [] - out = Enum.map(emoji, &build_emoji_tag/1) + out = Enum.map(emoji, &Emoji.build_emoji_tag/1) Map.put(object, "tag", tags ++ out) end def add_emoji_tags(object), do: object - def build_emoji_tag({name, url}) do - url = URI.encode(url) - - %{ - "icon" => %{"url" => "#{url}", "type" => "Image"}, - "name" => ":" <> name <> ":", - "type" => "Emoji", - "updated" => "1970-01-01T00:00:00Z", - "id" => url - } - end - def set_conversation(object) do Map.put(object, "conversation", object["context"]) end diff --git a/test/pleroma/web/activity_pub/transmogrifier/emoji_tag_building_test.exs b/test/pleroma/web/activity_pub/transmogrifier/emoji_tag_building_test.exs index c632c199c..a6c5d689c 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/emoji_tag_building_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/emoji_tag_building_test.exs @@ -7,7 +7,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.EmojiTagBuildingTest do name = "hanapog" url = "https://misskey.local.live/emojis/hana pog.png" - tag = Transmogrifier.build_emoji_tag({name, url}) + tag = Pleroma.Emoji.build_emoji_tag({name, url}) assert tag["id"] == "https://misskey.local.live/emojis/hana%20pog.png" end diff --git a/test/pleroma/web/mastodon_api/views/notification_view_test.exs b/test/pleroma/web/mastodon_api/views/notification_view_test.exs index ce5ddd0fc..c8287bb79 100644 --- a/test/pleroma/web/mastodon_api/views/notification_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/notification_view_test.exs @@ -219,7 +219,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do data: %{ "reactions" => [ ["👍", [user.ap_id], nil], - ["dinosaur", [user.ap_id], "http://localhost:4001/emoji/dino walking.gif"] + ["dinosaur", [user.ap_id], "http://localhost:4001/emoji/dino%20walking.gif"] ] } ) @@ -243,7 +243,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do account: AccountView.render("show.json", %{user: other_user, for: user}), status: StatusView.render("show.json", %{activity: activity, for: user}), created_at: Utils.to_masto_date(notification.inserted_at), - emoji_url: "http://localhost:4001/emoji/dino walking.gif" + emoji_url: "http://localhost:4001/emoji/dino%20walking.gif" } test_notifications_rendering([notification], user, [expected]) diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs index d7908886b..73cab817b 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -54,7 +54,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do count: 2, me: false, name: "dinosaur", - url: "http://localhost:4001/emoji/dino walking.gif", + url: "http://localhost:4001/emoji/dino%20walking.gif", account_ids: [other_user.id, user.id] }, %{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]} @@ -70,7 +70,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do count: 2, me: true, name: "dinosaur", - url: "http://localhost:4001/emoji/dino walking.gif", + url: "http://localhost:4001/emoji/dino%20walking.gif", account_ids: [other_user.id, user.id] }, %{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]} diff --git a/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs index 79b805aca..8fcdb233b 100644 --- a/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs +++ b/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs @@ -100,7 +100,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do "name" => "dinosaur", "count" => 1, "me" => true, - "url" => "http://localhost:4001/emoji/dino walking.gif", + "url" => "http://localhost:4001/emoji/dino%20walking.gif", "account_ids" => [other_user.id] } ] From 2c20b3fc046a7bfaf9e75d3add2383998282e033 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Mon, 5 Jan 2026 12:12:21 +0400 Subject: [PATCH 08/40] Add changelog --- changelog.d/emoji-reaction-url-escape.fix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/emoji-reaction-url-escape.fix diff --git a/changelog.d/emoji-reaction-url-escape.fix b/changelog.d/emoji-reaction-url-escape.fix new file mode 100644 index 000000000..c3a1c8823 --- /dev/null +++ b/changelog.d/emoji-reaction-url-escape.fix @@ -0,0 +1 @@ +Encode custom emoji URLs in EmojiReact activity tags. From 19f3e2050eb77d9231bb3ca4c8bd056981e96018 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Tue, 6 Jan 2026 15:12:49 +0400 Subject: [PATCH 09/40] Emoji: Handle more edge cases for local emoji with strange filenames. --- lib/pleroma/emoji.ex | 19 +++++++++++- lib/pleroma/emoji/formatter.ex | 3 +- lib/pleroma/web/activity_pub/builder.ex | 3 +- .../mastodon_api/views/custom_emoji_view.ex | 3 +- .../pleroma_api/views/bookmark_folder_view.ex | 3 +- .../emoji_tag_building_test.exs | 30 +++++++++++++++++-- 6 files changed, 50 insertions(+), 11 deletions(-) diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index 8c0257ef4..9fead1010 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -11,6 +11,8 @@ defmodule Pleroma.Emoji do alias Pleroma.Emoji.Combinations alias Pleroma.Emoji.Loader + alias Pleroma.Utils.URIEncoding + alias Pleroma.Web.Endpoint require Logger @@ -189,8 +191,23 @@ defmodule Pleroma.Emoji do def emoji_url(_), do: nil + @spec local_url(String.t() | nil) :: String.t() | nil + def local_url(nil), do: nil + + def local_url("http" <> _ = url) do + URIEncoding.encode_url(url) + end + + def local_url("/" <> _ = path) do + URIEncoding.encode_url(Endpoint.url() <> path, bypass_decode: true) + end + + def local_url(path) when is_binary(path) do + local_url("/" <> path) + end + def build_emoji_tag({name, url}) do - url = URI.encode(url) + url = URIEncoding.encode_url(url) %{ "icon" => %{"url" => "#{url}", "type" => "Image"}, diff --git a/lib/pleroma/emoji/formatter.ex b/lib/pleroma/emoji/formatter.ex index 87fd35f13..9238bf912 100644 --- a/lib/pleroma/emoji/formatter.ex +++ b/lib/pleroma/emoji/formatter.ex @@ -5,7 +5,6 @@ defmodule Pleroma.Emoji.Formatter do alias Pleroma.Emoji alias Pleroma.HTML - alias Pleroma.Web.Endpoint alias Pleroma.Web.MediaProxy def emojify(text) do @@ -44,7 +43,7 @@ defmodule Pleroma.Emoji.Formatter do Emoji.get_all() |> Enum.filter(fn {emoji, %Emoji{}} -> String.contains?(text, ":#{emoji}:") end) |> Enum.reduce(%{}, fn {name, %Emoji{file: file}}, acc -> - Map.put(acc, name, to_string(URI.merge(Endpoint.url(), file))) + Map.put(acc, name, Emoji.local_url(file)) end) end diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 4146fd6ab..167c769a9 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -17,7 +17,6 @@ defmodule Pleroma.Web.ActivityPub.Builder do alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.CommonAPI.ActivityDraft - alias Pleroma.Web.Endpoint require Pleroma.Constants @@ -105,7 +104,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do defp local_custom_emoji_react(data, emoji) do with %{file: path} = emojo <- Emoji.get(emoji) do - url = "#{Endpoint.url()}#{path}" + url = Emoji.local_url(path) add_emoji_content(data, emojo.code, url) else _ -> {:error, "Emoji does not exist"} diff --git a/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex b/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex index cd59ab946..6ef48e98a 100644 --- a/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex +++ b/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex @@ -6,14 +6,13 @@ defmodule Pleroma.Web.MastodonAPI.CustomEmojiView do use Pleroma.Web, :view alias Pleroma.Emoji - alias Pleroma.Web.Endpoint def render("index.json", %{custom_emojis: custom_emojis}) do render_many(custom_emojis, __MODULE__, "show.json") end def render("show.json", %{custom_emoji: {shortcode, %Emoji{file: relative_url, tags: tags}}}) do - url = Endpoint.url() |> URI.merge(relative_url) |> to_string() + url = Emoji.local_url(relative_url) %{ "shortcode" => shortcode, diff --git a/lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex b/lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex index 12decb816..29028781f 100644 --- a/lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex +++ b/lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex @@ -7,7 +7,6 @@ defmodule Pleroma.Web.PleromaAPI.BookmarkFolderView do alias Pleroma.BookmarkFolder alias Pleroma.Emoji - alias Pleroma.Web.Endpoint def render("show.json", %{folder: %BookmarkFolder{} = folder}) do %{ @@ -33,7 +32,7 @@ defmodule Pleroma.Web.PleromaAPI.BookmarkFolderView do emoji = Emoji.get(emoji) if emoji != nil do - Endpoint.url() |> URI.merge(emoji.file) |> to_string() + Emoji.local_url(emoji.file) else nil end diff --git a/test/pleroma/web/activity_pub/transmogrifier/emoji_tag_building_test.exs b/test/pleroma/web/activity_pub/transmogrifier/emoji_tag_building_test.exs index a6c5d689c..ff005b466 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/emoji_tag_building_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/emoji_tag_building_test.exs @@ -1,8 +1,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.EmojiTagBuildingTest do use Pleroma.DataCase, async: true - alias Pleroma.Web.ActivityPub.Transmogrifier - test "it encodes the id to be a valid url" do name = "hanapog" url = "https://misskey.local.live/emojis/hana pog.png" @@ -11,4 +9,32 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.EmojiTagBuildingTest do assert tag["id"] == "https://misskey.local.live/emojis/hana%20pog.png" end + + test "it does not double-encode already encoded urls" do + name = "hanapog" + url = "https://misskey.local.live/emojis/hana%20pog.png" + + tag = Pleroma.Emoji.build_emoji_tag({name, url}) + + assert tag["id"] == url + end + + test "it encodes disallowed path characters" do + name = "hanapog" + url = "https://example.com/emojis/hana[pog].png" + + tag = Pleroma.Emoji.build_emoji_tag({name, url}) + + assert tag["id"] == "https://example.com/emojis/hana%5Bpog%5D.png" + end + + test "local_url does not decode percent in filenames" do + url = Pleroma.Emoji.local_url("/emoji/hana%20pog.png") + + assert url == Pleroma.Web.Endpoint.url() <> "/emoji/hana%2520pog.png" + + tag = Pleroma.Emoji.build_emoji_tag({"hanapog", url}) + + assert tag["id"] == url + end end From ee19d14b0619b04d75b980fa3235a397e372390d Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Tue, 6 Jan 2026 15:38:15 +0400 Subject: [PATCH 10/40] Linting --- lib/pleroma/web/activity_pub/transmogrifier.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index a4b1ae349..ba6f4030d 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -8,8 +8,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do """ @behaviour Pleroma.Web.ActivityPub.Transmogrifier.API alias Pleroma.Activity - alias Pleroma.Emoji alias Pleroma.EctoType.ActivityPub.ObjectValidators + alias Pleroma.Emoji alias Pleroma.Maps alias Pleroma.Object alias Pleroma.Object.Containment From 3ef98652f7b6613d543fa5b575e2f637cbb846c0 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Wed, 7 Jan 2026 10:40:45 +0400 Subject: [PATCH 11/40] Emoji, AccountView, UtilController: Handle encoding of emoji --- lib/pleroma/emoji.ex | 3 ++- .../web/mastodon_api/views/account_view.ex | 15 ++++++++++++++- .../controllers/util_controller.ex | 11 +++++++++++ .../emoji_tag_building_test.exs | 6 ++++++ .../mastodon_api/views/account_view_test.exs | 19 +++++++++++++++++++ 5 files changed, 52 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index 9fead1010..2fe425955 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -199,7 +199,8 @@ defmodule Pleroma.Emoji do end def local_url("/" <> _ = path) do - URIEncoding.encode_url(Endpoint.url() <> path, bypass_decode: true) + path = URIEncoding.encode_url(path, bypass_parse: true, bypass_decode: true) + Endpoint.url() <> path end def local_url(path) when is_binary(path) do diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 03a2fc55a..d5f44676d 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do alias Pleroma.User alias Pleroma.UserNote alias Pleroma.UserRelationship + alias Pleroma.Utils.URIEncoding alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MediaProxy @@ -238,7 +239,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do emojis = Enum.map(user.emoji, fn {shortcode, raw_url} -> - url = MediaProxy.url(raw_url) + url = + raw_url + |> encode_emoji_url() + |> MediaProxy.url() %{ shortcode: shortcode, @@ -511,4 +515,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do # See https://git.pleroma.social/pleroma/pleroma-meta/-/issues/14 user.actor_type == "Service" || user.actor_type == "Group" end + + defp encode_emoji_url(nil), do: nil + defp encode_emoji_url("http" <> _ = url), do: URIEncoding.encode_url(url) + + defp encode_emoji_url("/" <> _ = path), + do: URIEncoding.encode_url(path, bypass_parse: true, bypass_decode: true) + + defp encode_emoji_url(path) when is_binary(path), + do: URIEncoding.encode_url(path, bypass_parse: true, bypass_decode: true) end diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index aeafa195d..45df3d31c 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -11,6 +11,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do alias Pleroma.Config alias Pleroma.Emoji alias Pleroma.Healthcheck + alias Pleroma.Utils.URIEncoding alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.Auth.WrapperAuthenticator, as: Authenticator @@ -180,12 +181,22 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do def emoji(conn, _params) do emoji = Enum.reduce(Emoji.get_all(), %{}, fn {code, %Emoji{file: file, tags: tags}}, acc -> + file = encode_emoji_url(file) Map.put(acc, code, %{image_url: file, tags: tags}) end) json(conn, emoji) end + defp encode_emoji_url(nil), do: nil + defp encode_emoji_url("http" <> _ = url), do: URIEncoding.encode_url(url) + + defp encode_emoji_url("/" <> _ = path), + do: URIEncoding.encode_url(path, bypass_parse: true, bypass_decode: true) + + defp encode_emoji_url(path) when is_binary(path), + do: URIEncoding.encode_url(path, bypass_parse: true, bypass_decode: true) + def update_notification_settings(%{assigns: %{user: user}} = conn, params) do with {:ok, _} <- User.update_notification_settings(user, params) do json(conn, %{status: "success"}) diff --git a/test/pleroma/web/activity_pub/transmogrifier/emoji_tag_building_test.exs b/test/pleroma/web/activity_pub/transmogrifier/emoji_tag_building_test.exs index ff005b466..b91658ce4 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/emoji_tag_building_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/emoji_tag_building_test.exs @@ -37,4 +37,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.EmojiTagBuildingTest do assert tag["id"] == url end + + test "local_url encodes question marks in filenames" do + url = Pleroma.Emoji.local_url("/emoji/file?name.png") + + assert url == Pleroma.Web.Endpoint.url() <> "/emoji/file%3Fname.png" + end end diff --git a/test/pleroma/web/mastodon_api/views/account_view_test.exs b/test/pleroma/web/mastodon_api/views/account_view_test.exs index 5d24c0e9f..bd151cc61 100644 --- a/test/pleroma/web/mastodon_api/views/account_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/account_view_test.exs @@ -105,6 +105,25 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do assert expected == AccountView.render("show.json", %{user: user, skip_visibility_check: true}) end + test "encodes emoji urls in the emojis field" do + user = + insert(:user, + name: ":brackets: :percent:", + emoji: %{ + "brackets" => "/emoji/hana[pog].png", + "percent" => "/emoji/hana%20pog.png" + } + ) + + %{emojis: emojis} = + AccountView.render("show.json", %{user: user, skip_visibility_check: true}) + + emoji_urls = Map.new(emojis, &{&1.shortcode, &1.url}) + + assert emoji_urls["brackets"] == "/emoji/hana%5Bpog%5D.png" + assert emoji_urls["percent"] == "/emoji/hana%2520pog.png" + end + describe "roles and privileges" do setup do clear_config([:instance, :moderator_privileges], [:cofe, :only_moderator]) From 9ed6d78cb58daa1eaf1c266c4d50c4372b654149 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Wed, 7 Jan 2026 11:14:45 +0400 Subject: [PATCH 12/40] Linting --- lib/pleroma/web/twitter_api/controllers/util_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 45df3d31c..1c072f98a 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -11,8 +11,8 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do alias Pleroma.Config alias Pleroma.Emoji alias Pleroma.Healthcheck - alias Pleroma.Utils.URIEncoding alias Pleroma.User + alias Pleroma.Utils.URIEncoding alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.Auth.WrapperAuthenticator, as: Authenticator alias Pleroma.Web.CommonAPI From 227c7fafa0228d3c65a36d661bdfb0e6c918ef19 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Wed, 7 Jan 2026 17:59:37 +0400 Subject: [PATCH 13/40] Tests: Syncify tests that mutate global state. --- test/mix/pleroma_test.exs | 2 +- test/mix/tasks/pleroma/app_test.exs | 2 +- test/mix/tasks/pleroma/database_test.exs | 2 +- test/pleroma/http_test.exs | 2 +- .../pleroma/repo/migrations/autolinker_to_linkify_test.exs | 2 +- .../web/activity_pub/mrf/remote_report_policy_test.exs | 2 +- test/pleroma/web/activity_pub/utils_test.exs | 2 +- test/pleroma/web/activity_pub/views/user_view_test.exs | 2 +- .../pleroma_api/views/chat_message_reference_view_test.exs | 7 ++++++- test/pleroma/web/rich_media/card_test.exs | 4 +++- test/pleroma/web/web_finger_test.exs | 3 ++- test/pleroma/workers/publisher_worker_test.exs | 2 +- test/pleroma/workers/reachability_worker_test.exs | 2 +- test/pleroma/workers/receiver_worker_test.exs | 2 +- test/pleroma/workers/remote_fetcher_worker_test.exs | 2 +- 15 files changed, 23 insertions(+), 15 deletions(-) diff --git a/test/mix/pleroma_test.exs b/test/mix/pleroma_test.exs index e362223b2..e8f801913 100644 --- a/test/mix/pleroma_test.exs +++ b/test/mix/pleroma_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Mix.PleromaTest do - use ExUnit.Case, async: true + use ExUnit.Case, async: false import Mix.Pleroma setup_all do diff --git a/test/mix/tasks/pleroma/app_test.exs b/test/mix/tasks/pleroma/app_test.exs index 65245eadd..6156214c1 100644 --- a/test/mix/tasks/pleroma/app_test.exs +++ b/test/mix/tasks/pleroma/app_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Mix.Tasks.Pleroma.AppTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false setup_all do Mix.shell(Mix.Shell.Process) diff --git a/test/mix/tasks/pleroma/database_test.exs b/test/mix/tasks/pleroma/database_test.exs index 38ed096ae..19df17b60 100644 --- a/test/mix/tasks/pleroma/database_test.exs +++ b/test/mix/tasks/pleroma/database_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Mix.Tasks.Pleroma.DatabaseTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false use Oban.Testing, repo: Pleroma.Repo alias Pleroma.Activity diff --git a/test/pleroma/http_test.exs b/test/pleroma/http_test.exs index 7b6847cf9..e673e6591 100644 --- a/test/pleroma/http_test.exs +++ b/test/pleroma/http_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.HTTPTest do - use ExUnit.Case, async: true + use ExUnit.Case, async: false use Pleroma.Tests.Helpers import Tesla.Mock diff --git a/test/pleroma/repo/migrations/autolinker_to_linkify_test.exs b/test/pleroma/repo/migrations/autolinker_to_linkify_test.exs index 99522994a..c15967eee 100644 --- a/test/pleroma/repo/migrations/autolinker_to_linkify_test.exs +++ b/test/pleroma/repo/migrations/autolinker_to_linkify_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Repo.Migrations.AutolinkerToLinkifyTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false import Pleroma.Factory import Pleroma.Tests.Helpers alias Pleroma.ConfigDB diff --git a/test/pleroma/web/activity_pub/mrf/remote_report_policy_test.exs b/test/pleroma/web/activity_pub/mrf/remote_report_policy_test.exs index 8d2a6b4fa..270c650e0 100644 --- a/test/pleroma/web/activity_pub/mrf/remote_report_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/remote_report_policy_test.exs @@ -1,5 +1,5 @@ defmodule Pleroma.Web.ActivityPub.MRF.RemoteReportPolicyTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false alias Pleroma.Web.ActivityPub.MRF.RemoteReportPolicy diff --git a/test/pleroma/web/activity_pub/utils_test.exs b/test/pleroma/web/activity_pub/utils_test.exs index a48639c38..f162f3684 100644 --- a/test/pleroma/web/activity_pub/utils_test.exs +++ b/test/pleroma/web/activity_pub/utils_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.UtilsTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false alias Pleroma.Activity alias Pleroma.Object alias Pleroma.Repo diff --git a/test/pleroma/web/activity_pub/views/user_view_test.exs b/test/pleroma/web/activity_pub/views/user_view_test.exs index 7ac5f7c0f..a3f807ca9 100644 --- a/test/pleroma/web/activity_pub/views/user_view_test.exs +++ b/test/pleroma/web/activity_pub/views/user_view_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.UserViewTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false import Pleroma.Factory alias Pleroma.User diff --git a/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs b/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs index c78c03aba..8c3682798 100644 --- a/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs +++ b/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatMessageReferenceViewTest do alias Pleroma.NullCache - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false alias Pleroma.Chat alias Pleroma.Chat.MessageReference @@ -18,6 +18,11 @@ defmodule Pleroma.Web.PleromaAPI.ChatMessageReferenceViewTest do import Mox import Pleroma.Factory + setup do + Mox.stub_with(Pleroma.CachexMock, Pleroma.NullCache) + :ok + end + setup do: clear_config([:rich_media, :enabled], true) test "it displays a chat message" do diff --git a/test/pleroma/web/rich_media/card_test.exs b/test/pleroma/web/rich_media/card_test.exs index c69f85323..723446c86 100644 --- a/test/pleroma/web/rich_media/card_test.exs +++ b/test/pleroma/web/rich_media/card_test.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Web.RichMedia.CardTest do use Oban.Testing, repo: Pleroma.Repo - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false alias Pleroma.Tests.ObanHelpers alias Pleroma.UnstubbedConfigMock, as: ConfigMock @@ -19,6 +19,8 @@ defmodule Pleroma.Web.RichMedia.CardTest do setup do mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) + Mox.stub_with(Pleroma.CachexMock, Pleroma.NullCache) + ConfigMock |> stub_with(Pleroma.Test.StaticConfig) diff --git a/test/pleroma/web/web_finger_test.exs b/test/pleroma/web/web_finger_test.exs index eb03c736e..da10abdd2 100644 --- a/test/pleroma/web/web_finger_test.exs +++ b/test/pleroma/web/web_finger_test.exs @@ -3,12 +3,13 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.WebFingerTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false alias Pleroma.Web.WebFinger import Pleroma.Factory import Tesla.Mock setup do + Mox.stub_with(Pleroma.CachexMock, Pleroma.NullCache) mock(fn env -> apply(HttpRequestMock, :request, [env]) end) :ok end diff --git a/test/pleroma/workers/publisher_worker_test.exs b/test/pleroma/workers/publisher_worker_test.exs index ca432d9bf..4519864a5 100644 --- a/test/pleroma/workers/publisher_worker_test.exs +++ b/test/pleroma/workers/publisher_worker_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Workers.PublisherWorkerTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false use Oban.Testing, repo: Pleroma.Repo import Pleroma.Factory diff --git a/test/pleroma/workers/reachability_worker_test.exs b/test/pleroma/workers/reachability_worker_test.exs index 4854aff77..c641ccf65 100644 --- a/test/pleroma/workers/reachability_worker_test.exs +++ b/test/pleroma/workers/reachability_worker_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Workers.ReachabilityWorkerTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false use Oban.Testing, repo: Pleroma.Repo import Mock diff --git a/test/pleroma/workers/receiver_worker_test.exs b/test/pleroma/workers/receiver_worker_test.exs index 7f4789f91..12abc1a27 100644 --- a/test/pleroma/workers/receiver_worker_test.exs +++ b/test/pleroma/workers/receiver_worker_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Workers.ReceiverWorkerTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false use Oban.Testing, repo: Pleroma.Repo import Mock diff --git a/test/pleroma/workers/remote_fetcher_worker_test.exs b/test/pleroma/workers/remote_fetcher_worker_test.exs index 6eb6932cb..a3b900a9b 100644 --- a/test/pleroma/workers/remote_fetcher_worker_test.exs +++ b/test/pleroma/workers/remote_fetcher_worker_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Workers.RemoteFetcherWorkerTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false use Oban.Testing, repo: Pleroma.Repo alias Pleroma.Workers.RemoteFetcherWorker From 0ec0ad85598ef42ca05d6407868bb5a2fd3ce18f Mon Sep 17 00:00:00 2001 From: floatingghost Date: Sat, 4 Feb 2023 20:51:17 +0000 Subject: [PATCH 14/40] paginate follow requests (#460) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit matches https://docs.joinmastodon.org/methods/follow_requests/#get mostly Co-authored-by: FloatingGhost Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma/pulls/460 Signed-off-by: nicole mikołajczyk --- lib/pleroma/following_relationship.ex | 18 +++++++-------- lib/pleroma/user.ex | 10 +++++++-- .../operations/follow_request_operation.ex | 19 ++++++++++++++++ .../pleroma_follow_request_operation.ex | 21 +++++++++++++++++- .../controllers/follow_request_controller.ex | 15 ++++++++++--- .../web/mastodon_api/views/account_view.ex | 3 ++- .../controllers/follow_request_controller.ex | 10 ++++++++- .../follow_request_controller_test.exs | 22 +++++++++++++++++++ 8 files changed, 100 insertions(+), 18 deletions(-) diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index 653feb32f..e27f78d33 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -147,24 +147,22 @@ defmodule Pleroma.FollowingRelationship do |> Repo.aggregate(:count, :id) end - def get_follow_requests(%User{id: id}) do + def get_follow_requests_query(%User{id: id}) do __MODULE__ - |> join(:inner, [r], f in assoc(r, :follower)) + |> join(:inner, [r], f in assoc(r, :follower), as: :follower) |> where([r], r.state == ^:follow_pending) |> where([r], r.following_id == ^id) - |> where([r, f], f.is_active == true) - |> select([r, f], f) - |> Repo.all() + |> where([r, follower: f], f.is_active == true) + |> select([r, follower: f], f) end - def get_outgoing_follow_requests(%User{id: id}) do + def get_outgoing_follow_requests_query(%User{id: id}) do __MODULE__ - |> join(:inner, [r], f in assoc(r, :following)) + |> join(:inner, [r], f in assoc(r, :following), as: :following) |> where([r], r.state == ^:follow_pending) |> where([r], r.follower_id == ^id) - |> where([r, f], f.is_active == true) - |> select([r, f], f) - |> Repo.all() + |> where([r, following: f], f.is_active == true) + |> select([r, following: f], f) end def following?(%User{id: follower_id}, %User{id: followed_id}) do diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 904e9e056..75da41da9 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -287,8 +287,14 @@ defmodule Pleroma.User do defdelegate following(user), to: FollowingRelationship defdelegate following?(follower, followed), to: FollowingRelationship defdelegate following_ap_ids(user), to: FollowingRelationship - defdelegate get_follow_requests(user), to: FollowingRelationship - defdelegate get_outgoing_follow_requests(user), to: FollowingRelationship + defdelegate get_follow_requests_query(user), to: FollowingRelationship + defdelegate get_outgoing_follow_requests_query(user), to: FollowingRelationship + + def get_follow_requests(user) do + get_follow_requests_query(user) + |> Repo.all() + end + defdelegate search(query, opts \\ []), to: User.Search @doc """ diff --git a/lib/pleroma/web/api_spec/operations/follow_request_operation.ex b/lib/pleroma/web/api_spec/operations/follow_request_operation.ex index 72dc8b5fa..fbb997447 100644 --- a/lib/pleroma/web/api_spec/operations/follow_request_operation.ex +++ b/lib/pleroma/web/api_spec/operations/follow_request_operation.ex @@ -19,6 +19,7 @@ defmodule Pleroma.Web.ApiSpec.FollowRequestOperation do summary: "Retrieve follow requests", security: [%{"oAuth" => ["read:follows", "follow"]}], operationId: "FollowRequestController.index", + parameters: pagination_params(), responses: %{ 200 => Operation.response("Array of Account", "application/json", %Schema{ @@ -62,4 +63,22 @@ defmodule Pleroma.Web.ApiSpec.FollowRequestOperation do required: true ) end + + defp pagination_params do + [ + Operation.parameter(:max_id, :query, :string, "Return items older than this ID"), + Operation.parameter( + :since_id, + :query, + :string, + "Return the oldest items newer than this ID" + ), + Operation.parameter( + :limit, + :query, + %Schema{type: :integer, default: 20}, + "Maximum number of items to return. Will be ignored if it's more than 40" + ) + ] + end end diff --git a/lib/pleroma/web/api_spec/operations/pleroma_follow_request_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_follow_request_operation.ex index b5a413490..5e2cf6a78 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_follow_request_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_follow_request_operation.ex @@ -17,7 +17,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaFollowRequestOperation do tags: ["Follow requests"], summary: "Retrieve outgoing follow requests", security: [%{"oAuth" => ["read:follows", "follow"]}], - operationId: "PleromaFollowRequestController.outgoing", + operationId: "PleromaFollowRequestController.outgoing",, + parameters: pagination_params(), responses: %{ 200 => Operation.response("Array of Account", "application/json", %Schema{ @@ -28,4 +29,22 @@ defmodule Pleroma.Web.ApiSpec.PleromaFollowRequestOperation do } } end + + defp pagination_params do + [ + Operation.parameter(:max_id, :query, :string, "Return items older than this ID"), + Operation.parameter( + :since_id, + :query, + :string, + "Return the oldest items newer than this ID" + ), + Operation.parameter( + :limit, + :query, + %Schema{type: :integer, default: 20}, + "Maximum number of items to return. Will be ignored if it's more than 40" + ) + ] + end end diff --git a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex index 6eee55d1b..a15029d92 100644 --- a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex @@ -5,6 +5,10 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do use Pleroma.Web, :controller + import Pleroma.Web.ControllerHelper, + only: [add_link_headers: 2] + + alias Pleroma.Pagination alias Pleroma.User alias Pleroma.Web.CommonAPI alias Pleroma.Web.Plugs.OAuthScopesPlug @@ -24,10 +28,15 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.FollowRequestOperation @doc "GET /api/v1/follow_requests" - def index(%{assigns: %{user: followed}} = conn, _params) do - follow_requests = User.get_follow_requests(followed) + def index(%{assigns: %{user: followed}} = conn, params) do + follow_requests = + followed + |> User.get_follow_requests_query() + |> Pagination.fetch_paginated(params, :keyset, :follower) - render(conn, "index.json", for: followed, users: follow_requests, as: :user) + conn + |> add_link_headers(follow_requests) + |> render("index.json", for: followed, users: follow_requests, as: :user) end @doc "POST /api/v1/follow_requests/:id/authorize" diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 03a2fc55a..696880dc8 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -356,7 +356,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do %User{id: user_id} ) do count = - User.get_follow_requests(user) + user + |> User.get_follow_requests() |> length() data diff --git a/lib/pleroma/web/pleroma_api/controllers/follow_request_controller.ex b/lib/pleroma/web/pleroma_api/controllers/follow_request_controller.ex index 656d477da..387309cd8 100644 --- a/lib/pleroma/web/pleroma_api/controllers/follow_request_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/follow_request_controller.ex @@ -5,6 +5,10 @@ defmodule Pleroma.Web.PleromaAPI.FollowRequestController do use Pleroma.Web, :controller + import Pleroma.Web.ControllerHelper, + only: [add_link_headers: 2] + + alias Pleroma.Pagination alias Pleroma.User alias Pleroma.Web.Plugs.OAuthScopesPlug @@ -18,10 +22,14 @@ defmodule Pleroma.Web.PleromaAPI.FollowRequestController do @doc "GET /api/v1/pleroma/outgoing_follow_requests" def outgoing(%{assigns: %{user: follower}} = conn, _params) do - follow_requests = User.get_outgoing_follow_requests(follower) + follow_requests = + follower + |> User.get_outgoing_follow_requests_query() + |> Pagination.fetch_paginated(params, :keyset, :follower) conn |> put_view(Pleroma.Web.MastodonAPI.FollowRequestView) + |> add_link_headers(follow_requests) |> render("index.json", for: follower, users: follow_requests, as: :user) end end diff --git a/test/pleroma/web/mastodon_api/controllers/follow_request_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/follow_request_controller_test.exs index b7c7ccae0..276866d75 100644 --- a/test/pleroma/web/mastodon_api/controllers/follow_request_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/follow_request_controller_test.exs @@ -10,6 +10,11 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do import Pleroma.Factory + defp extract_next_link_header(header) do + [_, next_link] = Regex.run(~r{<(?.*)>; rel="next"}, header) + next_link + end + describe "locked accounts" do setup do user = insert(:user, is_locked: true) @@ -31,6 +36,23 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do assert to_string(other_user.id) == relationship["id"] end + test "/api/v1/follow_requests paginates", %{user: user, conn: conn} do + for _ <- 1..21 do + other_user = insert(:user) + {:ok, _, _, _activity} = CommonAPI.follow(other_user, user) + {:ok, _, _} = User.follow(other_user, user, :follow_pending) + end + + conn = get(conn, "/api/v1/follow_requests") + assert length(json_response_and_validate_schema(conn, 200)) == 20 + assert [link_header] = get_resp_header(conn, "link") + assert link_header =~ "rel=\"next\"" + next_link = extract_next_link_header(link_header) + assert next_link =~ "/api/v1/follow_requests" + conn = get(conn, next_link) + assert length(json_response_and_validate_schema(conn, 200)) == 1 + end + test "/api/v1/follow_requests/:id/authorize works", %{user: user, conn: conn} do other_user = insert(:user) From 7b134e7aa51e112cf1fb903a4d0497be861ec0d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Tue, 14 Oct 2025 07:38:59 +0200 Subject: [PATCH 15/40] optimize follow_request_count for own account view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- lib/pleroma/web/mastodon_api/views/account_view.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 696880dc8..ff824e698 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -357,8 +357,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do ) do count = user - |> User.get_follow_requests() - |> length() + |> User.get_follow_requests_query() + |> Pleroma.Repo.aggregate(:count) data |> Kernel.put_in([:follow_requests_count], count) From b2469404a9f905226943e3d7be639ebeb650110b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Wed, 7 Jan 2026 15:34:31 +0100 Subject: [PATCH 16/40] Add changelog entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- changelog.d/paginate-follow-requests.change | 1 + .../api_spec/operations/pleroma_follow_request_operation.ex | 2 +- .../web/pleroma_api/controllers/follow_request_controller.ex | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 changelog.d/paginate-follow-requests.change diff --git a/changelog.d/paginate-follow-requests.change b/changelog.d/paginate-follow-requests.change new file mode 100644 index 000000000..1a88995b7 --- /dev/null +++ b/changelog.d/paginate-follow-requests.change @@ -0,0 +1 @@ +Paginate follow requests diff --git a/lib/pleroma/web/api_spec/operations/pleroma_follow_request_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_follow_request_operation.ex index 5e2cf6a78..b3fa0457d 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_follow_request_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_follow_request_operation.ex @@ -17,7 +17,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaFollowRequestOperation do tags: ["Follow requests"], summary: "Retrieve outgoing follow requests", security: [%{"oAuth" => ["read:follows", "follow"]}], - operationId: "PleromaFollowRequestController.outgoing",, + operationId: "PleromaFollowRequestController.outgoing", parameters: pagination_params(), responses: %{ 200 => diff --git a/lib/pleroma/web/pleroma_api/controllers/follow_request_controller.ex b/lib/pleroma/web/pleroma_api/controllers/follow_request_controller.ex index 387309cd8..c72b6941b 100644 --- a/lib/pleroma/web/pleroma_api/controllers/follow_request_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/follow_request_controller.ex @@ -21,11 +21,11 @@ defmodule Pleroma.Web.PleromaAPI.FollowRequestController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaFollowRequestOperation @doc "GET /api/v1/pleroma/outgoing_follow_requests" - def outgoing(%{assigns: %{user: follower}} = conn, _params) do + def outgoing(%{assigns: %{user: follower}} = conn, params) do follow_requests = follower |> User.get_outgoing_follow_requests_query() - |> Pagination.fetch_paginated(params, :keyset, :follower) + |> Pagination.fetch_paginated(params, :keyset, :following) conn |> put_view(Pleroma.Web.MastodonAPI.FollowRequestView) From e671ca2554cf2a99970fdb2c208db02b3ac172cc Mon Sep 17 00:00:00 2001 From: Phantasm Date: Wed, 7 Jan 2026 20:41:02 +0100 Subject: [PATCH 17/40] Add Oban Web and upgrade LiveView, plug --- lib/pleroma/web/router.ex | 2 ++ mix.exs | 3 ++- mix.lock | 6 ++++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index da9626147..c5ee26f20 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.Router do use Pleroma.Web, :router import Phoenix.LiveDashboard.Router + import Oban.Web.Router pipeline :accepts_html do plug(:accepts, ["html"]) @@ -1044,6 +1045,7 @@ defmodule Pleroma.Web.Router do scope "/" do pipe_through([:pleroma_html, :authenticate, :require_admin]) live_dashboard("/phoenix/live_dashboard", additional_pages: [oban: Oban.LiveDashboard]) + oban_dashboard("/pleroma/oban") end # Test-only routes needed to test action dispatching and plug chain execution diff --git a/mix.exs b/mix.exs index 9e7ce7d4e..7954d5644 100644 --- a/mix.exs +++ b/mix.exs @@ -130,7 +130,7 @@ defmodule Pleroma.Mixfile do {:ecto_enum, "~> 1.4"}, {:postgrex, ">= 0.20.0"}, {:phoenix_html, "~> 3.3"}, - {:phoenix_live_view, "~> 0.19.0"}, + {:phoenix_live_view, "~> 1.1.0"}, {:phoenix_live_dashboard, "~> 0.8.0"}, {:telemetry_metrics, "~> 0.6"}, {:telemetry_poller, "~> 1.0"}, @@ -140,6 +140,7 @@ defmodule Pleroma.Mixfile do {:oban_plugins_lazarus, git: "https://git.pleroma.social/pleroma/elixir-libraries/oban_plugins_lazarus.git", ref: "e49fc355baaf0e435208bf5f534d31e26e897711"}, + {:oban_web, "~> 2.11"}, {:gettext, "~> 0.20"}, {:bcrypt_elixir, "~> 2.2"}, {:trailing_format_plug, "~> 0.0.7"}, diff --git a/mix.lock b/mix.lock index 3599f62b4..c469f4f01 100644 --- a/mix.lock +++ b/mix.lock @@ -95,7 +95,9 @@ "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]}, "oban": {:hex, :oban, "2.19.4", "045adb10db1161dceb75c254782f97cdc6596e7044af456a59decb6d06da73c1", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:igniter, "~> 0.5", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5fcc6219e6464525b808d97add17896e724131f498444a292071bf8991c99f97"}, "oban_live_dashboard": {:hex, :oban_live_dashboard, "0.1.1", "8aa4ceaf381c818f7d5c8185cc59942b8ac82ef0cf559881aacf8d3f8ac7bdd3", [:mix], [{:oban, "~> 2.15", [hex: :oban, repo: "hexpm", optional: false]}, {:phoenix_live_dashboard, "~> 0.7", [hex: :phoenix_live_dashboard, repo: "hexpm", optional: false]}], "hexpm", "16dc4ce9c9a95aa2e655e35ed4e675652994a8def61731a18af85e230e1caa63"}, + "oban_met": {:hex, :oban_met, "1.0.5", "bb633ab06448dab2ef9194f6688d33b3d07fc3f2ad793a1a08f4dfbb2cc9fe50", [:mix], [{:oban, "~> 2.19", [hex: :oban, repo: "hexpm", optional: false]}], "hexpm", "64664d50805bbfd3903aeada1f3c39634652a87844797ee400b0bcc95a28f5ea"}, "oban_plugins_lazarus": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/oban_plugins_lazarus.git", "e49fc355baaf0e435208bf5f534d31e26e897711", [ref: "e49fc355baaf0e435208bf5f534d31e26e897711"]}, + "oban_web": {:hex, :oban_web, "2.11.6", "53933cb4253c4d9f1098ee311c06f07935259f0e564dcf2d66bae4cc98e317fe", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, "~> 2.19", [hex: :oban, repo: "hexpm", optional: false]}, {:oban_met, "~> 1.0", [hex: :oban_met, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "576d94b705688c313694c2c114ca21aa0f8f2ad1b9ca45c052c5ba316d3e8d10"}, "octo_fetch": {:hex, :octo_fetch, "0.4.0", "074b5ecbc08be10b05b27e9db08bc20a3060142769436242702931c418695b19", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "cf8be6f40cd519d7000bb4e84adcf661c32e59369ca2827c4e20042eda7a7fc6"}, "open_api_spex": {:hex, :open_api_spex, "3.22.0", "fbf90dc82681dc042a4ee79853c8e989efbba73d9e87439085daf849bbf8bc20", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0 or ~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "dd751ddbdd709bb4a5313e9a24530da6e66594773c7242a0c2592cbd9f589063"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, @@ -105,12 +107,12 @@ "phoenix_html": {:hex, :phoenix_html, "3.3.4", "42a09fc443bbc1da37e372a5c8e6755d046f22b9b11343bf885067357da21cb3", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0249d3abec3714aff3415e7ee3d9786cb325be3151e6c4b3021502c585bf53fb"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.7", "405880012cb4b706f26dd1c6349125bfc903fb9e44d1ea668adaf4e04d4884b7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3a8625cab39ec261d48a13b7468dc619c0ede099601b084e343968309bd4d7d7"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "0.19.5", "6e730595e8e9b8c5da230a814e557768828fd8dfeeb90377d2d8dbb52d4ec00a", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b2eaa0dd3cfb9bd7fb949b88217df9f25aed915e986a28ad5c8a0d054e7ca9d3"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "1.1.19", "c95e9acbc374fb796ee3e24bfecc8213123c74d9f9e45667ca40bb0a4d242953", [:mix], [{:igniter, ">= 0.6.16 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:lazy_html, "~> 0.1.0", [hex: :lazy_html, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d5ad357d6b21562a5b431f0ad09dfe76db9ce5648c6949f1aac334c8c4455d32"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, "phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.1", "b74ccaa8046fbc388a62134360ee7d9742d5a8ae74063f34eb050279de7a99e1", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "4000eeba3f9d7d1a6bf56d2bd56733d5cadf41a7f0d8ffe5bb67e7d667e204a2"}, "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, "phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"}, - "plug": {:hex, :plug, "1.18.1", "5067f26f7745b7e31bc3368bc1a2b818b9779faa959b49c934c17730efc911cf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "57a57db70df2b422b564437d2d33cf8d33cd16339c1edb190cd11b1a3a546cc2"}, + "plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"}, "plug_cowboy": {:hex, :plug_cowboy, "2.7.4", "729c752d17cf364e2b8da5bdb34fb5804f56251e88bb602aff48ae0bd8673d11", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9b85632bd7012615bae0a5d70084deb1b25d2bcbb32cab82d1e9a1e023168aa3"}, "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, "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"}, From 30839063ef82404091c1b72d00c66e50e3cec891 Mon Sep 17 00:00:00 2001 From: Phantasm Date: Wed, 7 Jan 2026 21:12:28 +0100 Subject: [PATCH 18/40] changelog --- changelog.d/oban-web.add | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/oban-web.add diff --git a/changelog.d/oban-web.add b/changelog.d/oban-web.add new file mode 100644 index 000000000..c59e2ebca --- /dev/null +++ b/changelog.d/oban-web.add @@ -0,0 +1 @@ +Added Oban Web dashboard located at /pleroma/oban From 619ff5b9e3dee50bdda0cb78ffbfee97f0a9a41b Mon Sep 17 00:00:00 2001 From: Phantasm Date: Thu, 8 Jan 2026 00:28:29 +0100 Subject: [PATCH 19/40] Remove /pleroma/oban and /phoenix/live_dashboard from API routes This is needed to prevent admin frontend overrides from misbehaving when overriding AdminFE located at /pleroma/admin, since API routes are interpreted as the first portion of their full path, ie: /api/v1/pleroma/admin -> /api --- lib/pleroma/web/router.ex | 6 ++++++ test/pleroma/web/plugs/frontend_static_plug_test.exs | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index c5ee26f20..37212017d 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -1091,9 +1091,15 @@ defmodule Pleroma.Web.Router do options("/*path", RedirectController, :empty) end + # /pleroma/oban/* needs to get filtered out from api routes for frontend configuration + # to not drop admin overrides for /pleroma/admin. + # Also removing /phoenix since it is not an API route + @non_api_routes ["/phoenix/live_dashboard", "/pleroma/oban"] + def get_api_routes do Phoenix.Router.routes(__MODULE__) |> Enum.reject(fn r -> r.plug == Pleroma.Web.Fallback.RedirectController end) + |> Enum.reject(fn r -> String.starts_with?(r.path, @non_api_routes) end) |> Enum.map(fn r -> r.path |> String.split("/", trim: true) diff --git a/test/pleroma/web/plugs/frontend_static_plug_test.exs b/test/pleroma/web/plugs/frontend_static_plug_test.exs index cbe200738..e1e331c06 100644 --- a/test/pleroma/web/plugs/frontend_static_plug_test.exs +++ b/test/pleroma/web/plugs/frontend_static_plug_test.exs @@ -106,7 +106,6 @@ defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do "manifest.json", "auth", "proxy", - "phoenix", "test", "user_exists", "check_password" From 100cfe4db83ecb7cfb64ccb2dd37147d5a4c91df Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Thu, 8 Jan 2026 13:39:55 +0400 Subject: [PATCH 20/40] Config: Make streaming in tests actually synchronous --- config/test.exs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/config/test.exs b/config/test.exs index 4da2b4f57..9652b7d4b 100644 --- a/config/test.exs +++ b/config/test.exs @@ -102,7 +102,6 @@ config :pleroma, :http, send_user_agent: false rum_enabled = System.get_env("RUM_ENABLED") == "true" config :pleroma, :database, rum_enabled: rum_enabled -IO.puts("RUM enabled: #{rum_enabled}") config :joken, default_signer: "yU8uHKq+yyAkZ11Hx//jcdacWc8yQ1bxAAGrplzB0Zwwjkp35v0RK9SO8WTPr6QZ" @@ -192,7 +191,7 @@ config :pleroma, Pleroma.Application, streamer_registry: false, test_http_pools: true -config :pleroma, Pleroma.Web.Streaming, sync_streaming: true +config :pleroma, Pleroma.Web.Streamer, sync_streaming: true config :pleroma, Pleroma.Uploaders.Uploader, timeout: 1_000 @@ -207,8 +206,9 @@ config :pleroma, Pleroma.User.Backup, tempdir: "test/tmp" if File.exists?("./config/test.secret.exs") do import_config "test.secret.exs" -else - IO.puts( - "You may want to create test.secret.exs to declare custom database connection parameters." - ) end + +# Avoid noisy shutdown logs from os_mon during tests. +config :os_mon, + start_cpu_sup: false, + start_memsup: false From 3ecc861fa7b7c7c1bfc72b7832924425bb4deaf2 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Thu, 8 Jan 2026 13:40:25 +0400 Subject: [PATCH 21/40] StripLocation, ReadDescription: Silence noisy errors. --- lib/mix/tasks/pleroma/config.ex | 8 ++++- .../filter/exiftool/read_description.ex | 32 +++++++++++-------- .../upload/filter/exiftool/strip_location.ex | 5 +-- .../filter/exiftool/strip_location_test.exs | 4 +-- 4 files changed, 30 insertions(+), 19 deletions(-) diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 8b3b2f18b..834b4fe14 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -330,7 +330,13 @@ defmodule Mix.Tasks.Pleroma.Config do |> Enum.each(&write_and_delete(&1, file, opts[:delete])) :ok = File.close(file) - System.cmd("mix", ["format", path]) + + # Ensure `mix format` runs in the same env as the current task and doesn't + # emit config-time stderr noise (e.g. dev secret warnings) into `mix test`. + System.cmd("mix", ["format", path], + env: [{"MIX_ENV", to_string(Mix.env())}], + stderr_to_stdout: true + ) end defp config_header, do: "import Config\r\n\r\n" diff --git a/lib/pleroma/upload/filter/exiftool/read_description.ex b/lib/pleroma/upload/filter/exiftool/read_description.ex index 8c1ed82f8..8283b8643 100644 --- a/lib/pleroma/upload/filter/exiftool/read_description.ex +++ b/lib/pleroma/upload/filter/exiftool/read_description.ex @@ -29,22 +29,26 @@ defmodule Pleroma.Upload.Filter.Exiftool.ReadDescription do do: current_description defp read_when_empty(_, file, tag) do - try do - {tag_content, 0} = - System.cmd("exiftool", ["-b", "-s3", tag, file], - stderr_to_stdout: false, - parallelism: true - ) + if File.exists?(file) do + try do + {tag_content, 0} = + System.cmd("exiftool", ["-m", "-b", "-s3", tag, file], + stderr_to_stdout: false, + parallelism: true + ) - tag_content = String.trim(tag_content) + tag_content = String.trim(tag_content) - if tag_content != "" and - String.length(tag_content) <= - Pleroma.Config.get([:instance, :description_limit]), - do: tag_content, - else: nil - rescue - _ in ErlangError -> nil + if tag_content != "" and + String.length(tag_content) <= + Pleroma.Config.get([:instance, :description_limit]), + do: tag_content, + else: nil + rescue + _ in ErlangError -> nil + end + else + nil end end end diff --git a/lib/pleroma/upload/filter/exiftool/strip_location.ex b/lib/pleroma/upload/filter/exiftool/strip_location.ex index 1744a286d..23346d234 100644 --- a/lib/pleroma/upload/filter/exiftool/strip_location.ex +++ b/lib/pleroma/upload/filter/exiftool/strip_location.ex @@ -16,11 +16,12 @@ defmodule Pleroma.Upload.Filter.Exiftool.StripLocation do def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do try do - case System.cmd("exiftool", ["-overwrite_original", "-gps:all=", "-png:all=", file], + case System.cmd("exiftool", ["-m", "-overwrite_original", "-gps:all=", "-png:all=", file], + stderr_to_stdout: true, parallelism: true ) do {_response, 0} -> {:ok, :filtered} - {error, 1} -> {:error, error} + {error, _} -> {:error, error} end rescue e in ErlangError -> diff --git a/test/pleroma/upload/filter/exiftool/strip_location_test.exs b/test/pleroma/upload/filter/exiftool/strip_location_test.exs index 4dcd4dce3..485060215 100644 --- a/test/pleroma/upload/filter/exiftool/strip_location_test.exs +++ b/test/pleroma/upload/filter/exiftool/strip_location_test.exs @@ -25,8 +25,8 @@ defmodule Pleroma.Upload.Filter.Exiftool.StripLocationTest do assert Filter.Exiftool.StripLocation.filter(upload) == {:ok, :filtered} - {exif_original, 0} = System.cmd("exiftool", ["test/fixtures/DSCN0010.#{type}"]) - {exif_filtered, 0} = System.cmd("exiftool", ["test/fixtures/DSCN0010_tmp.#{type}"]) + {exif_original, 0} = System.cmd("exiftool", ["-m", "test/fixtures/DSCN0010.#{type}"]) + {exif_filtered, 0} = System.cmd("exiftool", ["-m", "test/fixtures/DSCN0010_tmp.#{type}"]) assert String.match?(exif_original, ~r/GPS/) refute String.match?(exif_filtered, ~r/GPS/) From 07b0e6c1dc3634bc92dffb2d5e85453507dcbdaa Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Thu, 8 Jan 2026 13:40:43 +0400 Subject: [PATCH 22/40] Mix: Silence migrations --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 9e7ce7d4e..21ddcfd0c 100644 --- a/mix.exs +++ b/mix.exs @@ -230,7 +230,7 @@ defmodule Pleroma.Mixfile do "ecto.rollback": ["pleroma.ecto.rollback"], "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], "ecto.reset": ["ecto.drop", "ecto.setup"], - test: ["ecto.create --quiet", "ecto.migrate", "test --warnings-as-errors"], + test: ["ecto.create --quiet", "pleroma.ecto.migrate --quiet", "test --warnings-as-errors"], docs: ["pleroma.docs", "docs"], analyze: ["credo --strict --only=warnings,todo,fixme,consistency,readability"], copyright: &add_copyright/1, From 0b498833cd6a84d643f085bb985efdf714e59c63 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Thu, 8 Jan 2026 13:40:51 +0400 Subject: [PATCH 23/40] Add changelog --- changelog.d/reduce-flaky-tests.skip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/reduce-flaky-tests.skip diff --git a/changelog.d/reduce-flaky-tests.skip b/changelog.d/reduce-flaky-tests.skip new file mode 100644 index 000000000..0375762c0 --- /dev/null +++ b/changelog.d/reduce-flaky-tests.skip @@ -0,0 +1 @@ +Reduce the number of flaky tests by making them sync if they affect the global state, and silence noisy test output. From 4984aaa18398c22bef8a8de9d0ad271f474e076b Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Thu, 8 Jan 2026 14:06:24 +0400 Subject: [PATCH 24/40] Streamer: Fix Marker streaming bug, fix caching in tests. --- lib/pleroma/web/streamer.ex | 2 +- test/pleroma/web/streamer_test.exs | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index aba42ee78..120e94751 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -300,7 +300,7 @@ defmodule Pleroma.Web.Streamer do end) end - defp do_stream("user", item) do + defp do_stream("user", %Activity{} = item) do Logger.debug("Trying to push to users") recipient_topics = diff --git a/test/pleroma/web/streamer_test.exs b/test/pleroma/web/streamer_test.exs index 096ca2d2a..f5008f6b9 100644 --- a/test/pleroma/web/streamer_test.exs +++ b/test/pleroma/web/streamer_test.exs @@ -19,7 +19,12 @@ defmodule Pleroma.Web.StreamerTest do @moduletag needs_streamer: true, capture_log: true - setup do: clear_config([:instance, :skip_thread_containment]) + setup do + clear_config([:instance, :skip_thread_containment]) + Mox.stub_with(Pleroma.CachexMock, Pleroma.NullCache) + + :ok + end describe "get_topic/_ (unauthenticated)" do test "allows no stream" do From 39279292b259fbb819972ddd52fb88bae966292f Mon Sep 17 00:00:00 2001 From: Phantasm Date: Thu, 8 Jan 2026 22:23:38 +0100 Subject: [PATCH 25/40] Docs: Add admin documentation for LiveDashboard and Oban Web --- docs/administration/dashboards.md | 47 +++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 docs/administration/dashboards.md diff --git a/docs/administration/dashboards.md b/docs/administration/dashboards.md new file mode 100644 index 000000000..8e8987633 --- /dev/null +++ b/docs/administration/dashboards.md @@ -0,0 +1,47 @@ +# Dashboards + +Pleroma comes with two types of backend dashboards viewable to instance administrators: + +* [Phoenix LiveDashboard](https://hexdocs.pm/phoenix_live_dashboard/Phoenix.LiveDashboard.html) - A general system oriented dashboard for viewing statistics about Pleroma resource consumption, Pleroma's database and Pleroma's job processor (Oban). +* [Oban Web](https://hexdocs.pm/oban_web/overview.html) - A dashboard specific to Oban for viewing Oban statistics, managing jobs and job queues. + +!!! note + Both dashboards require working Websockets. + If your browser or web server don't support Websockets, both dashboards either won't update or will not display all information. + +## Phoenix LiveDashboard + +Instance administrators can access this dashboard at `/phoenix/live_dashboard`, giving a simple overview of software versions including Erlang and Elixir versions, instance uptime and resource consumption. + +This dashboard gives insights into the current state of the BEAM VM running Pleroma code and database statistics including basic diagnostics. +It can be useful for troubleshooting of some issues namely regarding database performance. + +### Relevant dashboard tabs + +* Home - A general overview of system information including software versions, uptime and memory BEAM memory consumption. +* OS Data - Information about the OS and system such as CPU load, memory usage and disk usage. +* Ecto Stats - Information about the Pleroma database. + - Diagnose - Basic database diagnostics, including a `bloat` warning when an index or a table have excessive bloat, which can lead to bad database performance. + - Bloat - A table showing size of "bloat" (unused wasted space) in database tables and indexes. Very high bloat size in the `activities` and `objects` tables can lead to bad performance especially on slower disks such as on most VPS providers. + - Db settings - A small list of PostgreSQL settings mostly relevant to database performance. + - Total table size - Shows sizes of all database tables including indexes sorted by size, useful for quickly checking overall database size. + - Long running queries - A list of of slow database queries and their duration. Multiple entries with duration in multiple seconds indicate a slowly performing database. +* Oban - Shows a list of all Oban jobs. + +!!! note + The DB bloat warning for `index 'oban_jobs::oban_jobs_args_index'` in Ecto Stats can be safely ignored. + +## Oban Web + +An advanced dashboard and management console viewable to instance administrators specifically for Oban, Pleroma's job processor. +It allows managing jobs, including force retrying failed jobs and job deletion. +It can be accessed at `/pleroma/oban`. + +!!! danger + This dashboard is very powerful! If you are unsure what a certain feature does, don't use it. + Changing individual queue state/settings in the "Queues" view is heavily discouraged. + +* Shows a real time chart of either a number of executed jobs, or job execution/wait time per a given time frame and the state/queue/worker. +* Shows a list of jobs in each state, their argument, number of attempts and execution/scheduled time. +* Selecting one or multiple jobs in the list allows performing actions like canceling/deleting and retrying. +* Clicking on a job shows a detailed view including the full argument, when it was inserted, information about its attempts, and performing actions on it. From 5e114931f546b142d87739124dd36628283017be Mon Sep 17 00:00:00 2001 From: Phantasm Date: Fri, 9 Jan 2026 11:41:12 +0100 Subject: [PATCH 26/40] Move LiveDashboard to /pleroma/live_dashboard --- changelog.d/phoenix-livedashboard-move.change | 1 + docs/administration/dashboards.md | 2 +- lib/pleroma/web/fallback/redirect_controller.ex | 5 +++++ lib/pleroma/web/router.ex | 8 ++++---- test/pleroma/web/fallback_test.exs | 4 ++++ 5 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 changelog.d/phoenix-livedashboard-move.change diff --git a/changelog.d/phoenix-livedashboard-move.change b/changelog.d/phoenix-livedashboard-move.change new file mode 100644 index 000000000..116b1523a --- /dev/null +++ b/changelog.d/phoenix-livedashboard-move.change @@ -0,0 +1 @@ +Moved Phoenix LiveDashboard to /pleroma/live_dashboard diff --git a/docs/administration/dashboards.md b/docs/administration/dashboards.md index 8e8987633..b95e0fac0 100644 --- a/docs/administration/dashboards.md +++ b/docs/administration/dashboards.md @@ -11,7 +11,7 @@ Pleroma comes with two types of backend dashboards viewable to instance administ ## Phoenix LiveDashboard -Instance administrators can access this dashboard at `/phoenix/live_dashboard`, giving a simple overview of software versions including Erlang and Elixir versions, instance uptime and resource consumption. +Instance administrators can access this dashboard at `/pleroma/live_dashboard`, giving a simple overview of software versions including Erlang and Elixir versions, instance uptime and resource consumption. This dashboard gives insights into the current state of the BEAM VM running Pleroma code and database statistics including basic diagnostics. It can be useful for troubleshooting of some issues namely regarding database performance. diff --git a/lib/pleroma/web/fallback/redirect_controller.ex b/lib/pleroma/web/fallback/redirect_controller.ex index 60fc15b9e..d75a95fb3 100644 --- a/lib/pleroma/web/fallback/redirect_controller.ex +++ b/lib/pleroma/web/fallback/redirect_controller.ex @@ -29,6 +29,11 @@ defmodule Pleroma.Web.Fallback.RedirectController do ) end + def live_dashboard(conn, _params) do + conn + |> redirect(to: "/pleroma/live_dashboard") + end + def redirector(conn, _params, code \\ 200) do {:ok, index_content} = File.read(index_file_path(conn)) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 37212017d..b5e184ac1 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -1044,7 +1044,7 @@ defmodule Pleroma.Web.Router do scope "/" do pipe_through([:pleroma_html, :authenticate, :require_admin]) - live_dashboard("/phoenix/live_dashboard", additional_pages: [oban: Oban.LiveDashboard]) + live_dashboard("/pleroma/live_dashboard", additional_pages: [oban: Oban.LiveDashboard]) oban_dashboard("/pleroma/oban") end @@ -1086,15 +1086,15 @@ defmodule Pleroma.Web.Router do get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta) match(:*, "/api/pleroma/*path", LegacyPleromaApiRerouterPlug, []) get("/api/*path", RedirectController, :api_not_implemented) + get("/phoenix/live_dashboard", RedirectController, :live_dashboard) get("/*path", RedirectController, :redirector_with_preload) options("/*path", RedirectController, :empty) end - # /pleroma/oban/* needs to get filtered out from api routes for frontend configuration + # /pleroma/{phoenix,oban}/* need to get filtered out from api routes for frontend configuration # to not drop admin overrides for /pleroma/admin. - # Also removing /phoenix since it is not an API route - @non_api_routes ["/phoenix/live_dashboard", "/pleroma/oban"] + @non_api_routes ["/pleroma/live_dashboard", "/pleroma/oban"] def get_api_routes do Phoenix.Router.routes(__MODULE__) diff --git a/test/pleroma/web/fallback_test.exs b/test/pleroma/web/fallback_test.exs index 9184cf8f1..6d0ba3d2a 100644 --- a/test/pleroma/web/fallback_test.exs +++ b/test/pleroma/web/fallback_test.exs @@ -77,6 +77,10 @@ defmodule Pleroma.Web.FallbackTest do assert redirected_to(get(conn, "/pleroma/admin")) =~ "/pleroma/admin/" end + test "GET /phoenix/live_dashboard -> /pleroma/live_dashboard", %{conn: conn} do + assert redirected_to(get(conn, "/phoenix/live_dashboard")) =~ "/pleroma/live_dashboard" + end + test "OPTIONS /*path", %{conn: conn} do assert conn |> options("/foo") From aa95855a7fb4c950dd5c0bf9f60db60be982271e Mon Sep 17 00:00:00 2001 From: MediaFormat Date: Sun, 11 Jan 2026 01:12:42 +0000 Subject: [PATCH 27/40] Change redirect_uris to accept array of strings --- .../web/api_spec/operations/admin/o_auth_app_operation.ex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex b/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex index 2b2496c26..3dbef62cd 100644 --- a/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex @@ -123,7 +123,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do name: %Schema{type: :string, description: "Application Name"}, scopes: %Schema{type: :array, items: %Schema{type: :string}, description: "oAuth scopes"}, redirect_uris: %Schema{ - type: :string, + type: :array, items: %Schema{type: :string}, description: "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter." }, @@ -141,7 +141,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do }, example: %{ "name" => "My App", - "redirect_uris" => "https://myapp.com/auth/callback", + "redirect_uris" => ["https://myapp.com/auth/callback"], "website" => "https://myapp.com/", "scopes" => ["read", "write"], "trusted" => true @@ -157,7 +157,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do name: %Schema{type: :string, description: "Application Name"}, scopes: %Schema{type: :array, items: %Schema{type: :string}, description: "oAuth scopes"}, redirect_uris: %Schema{ - type: :string, + type: :array, items: %Schema{type: :string}, description: "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter." }, @@ -175,7 +175,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do }, example: %{ "name" => "My App", - "redirect_uris" => "https://myapp.com/auth/callback", + "redirect_uris" => ["https://myapp.com/auth/callback"], "website" => "https://myapp.com/", "scopes" => ["read", "write"], "trusted" => true From 7da1d429a263041f8c1c28a7d4c19e36926a1ffb Mon Sep 17 00:00:00 2001 From: MediaFormat Date: Sun, 11 Jan 2026 01:15:55 +0000 Subject: [PATCH 28/40] add changelog.d entry --- changelog.d/oauth-registration-redirect_uris.fix | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 changelog.d/oauth-registration-redirect_uris.fix diff --git a/changelog.d/oauth-registration-redirect_uris.fix b/changelog.d/oauth-registration-redirect_uris.fix new file mode 100644 index 000000000..e69de29bb From 87f3459f8c2624617f942c546ea4bd4386780aac Mon Sep 17 00:00:00 2001 From: MediaFormat Date: Sun, 11 Jan 2026 05:34:17 +0000 Subject: [PATCH 29/40] fix field type, fix formatting --- .../web/api_spec/operations/admin/o_auth_app_operation.ex | 6 ++++-- lib/pleroma/web/o_auth/app.ex | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex b/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex index 3dbef62cd..5d4306754 100644 --- a/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex @@ -123,7 +123,8 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do name: %Schema{type: :string, description: "Application Name"}, scopes: %Schema{type: :array, items: %Schema{type: :string}, description: "oAuth scopes"}, redirect_uris: %Schema{ - type: :array, items: %Schema{type: :string}, + type: :array, + items: %Schema{type: :string}, description: "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter." }, @@ -157,7 +158,8 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do name: %Schema{type: :string, description: "Application Name"}, scopes: %Schema{type: :array, items: %Schema{type: :string}, description: "oAuth scopes"}, redirect_uris: %Schema{ - type: :array, items: %Schema{type: :string}, + type: :array, + items: %Schema{type: :string}, description: "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter." }, diff --git a/lib/pleroma/web/o_auth/app.ex b/lib/pleroma/web/o_auth/app.ex index 7661c2566..f1145d500 100644 --- a/lib/pleroma/web/o_auth/app.ex +++ b/lib/pleroma/web/o_auth/app.ex @@ -14,7 +14,7 @@ defmodule Pleroma.Web.OAuth.App do schema "apps" do field(:client_name, :string) - field(:redirect_uris, :string) + field(:redirect_uris, {:array, :string}) field(:scopes, {:array, :string}, default: []) field(:website, :string) field(:client_id, :string) From 12002830bc672f23ba7e30426230cc4182245035 Mon Sep 17 00:00:00 2001 From: MediaFormat Date: Sun, 11 Jan 2026 17:47:27 +0000 Subject: [PATCH 30/40] fix tests --- test/pleroma/web/o_auth/app_test.exs | 14 +++++++------- test/pleroma/web/o_auth/o_auth_controller_test.exs | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/pleroma/web/o_auth/app_test.exs b/test/pleroma/web/o_auth/app_test.exs index a69ba371e..f90bee358 100644 --- a/test/pleroma/web/o_auth/app_test.exs +++ b/test/pleroma/web/o_auth/app_test.exs @@ -10,20 +10,20 @@ defmodule Pleroma.Web.OAuth.AppTest do describe "get_or_make/2" do test "gets exist app" do - attrs = %{client_name: "Mastodon-Local", redirect_uris: "."} + attrs = %{client_name: "Mastodon-Local", redirect_uris: ["."]} app = insert(:oauth_app, Map.merge(attrs, %{scopes: ["read", "write"]})) {:ok, %App{} = exist_app} = App.get_or_make(attrs, []) assert exist_app == app end test "make app" do - attrs = %{client_name: "Mastodon-Local", redirect_uris: "."} + attrs = %{client_name: "Mastodon-Local", redirect_uris: ["."]} {:ok, %App{} = app} = App.get_or_make(attrs, ["write"]) assert app.scopes == ["write"] end test "gets exist app and updates scopes" do - attrs = %{client_name: "Mastodon-Local", redirect_uris: "."} + attrs = %{client_name: "Mastodon-Local", redirect_uris: ["."]} app = insert(:oauth_app, Map.merge(attrs, %{scopes: ["read", "write"]})) {:ok, %App{} = exist_app} = App.get_or_make(attrs, ["read", "write", "follow", "push"]) assert exist_app.id == app.id @@ -31,10 +31,10 @@ defmodule Pleroma.Web.OAuth.AppTest do end test "has unique client_id" do - insert(:oauth_app, client_name: "", redirect_uris: "", client_id: "boop") + insert(:oauth_app, client_name: "", redirect_uris: [""], client_id: "boop") error = - catch_error(insert(:oauth_app, client_name: "", redirect_uris: "", client_id: "boop")) + catch_error(insert(:oauth_app, client_name: "", redirect_uris: [""], client_id: "boop")) assert %Ecto.ConstraintError{} = error assert error.constraint == "apps_client_id_index" @@ -55,7 +55,7 @@ defmodule Pleroma.Web.OAuth.AppTest do end test "removes orphaned apps" do - attrs = %{client_name: "Mastodon-Local", redirect_uris: "."} + attrs = %{client_name: "Mastodon-Local", redirect_uris: ["."]} {:ok, %App{} = old_app} = App.get_or_make(attrs, ["write"]) # backdate the old app so it's within the threshold for being cleaned up @@ -66,7 +66,7 @@ defmodule Pleroma.Web.OAuth.AppTest do |> Pleroma.Repo.query([one_hour_ago, old_app.id]) # Create the new app after backdating the old one - attrs = %{client_name: "PleromaFE", redirect_uris: "."} + attrs = %{client_name: "PleromaFE", redirect_uris: ["."]} {:ok, %App{} = app} = App.get_or_make(attrs, ["write"]) # Ensure the new app has a recent timestamp diff --git a/test/pleroma/web/o_auth/o_auth_controller_test.exs b/test/pleroma/web/o_auth/o_auth_controller_test.exs index 260442771..3788b9c65 100644 --- a/test/pleroma/web/o_auth/o_auth_controller_test.exs +++ b/test/pleroma/web/o_auth/o_auth_controller_test.exs @@ -406,7 +406,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do describe "GET /oauth/authorize" do setup do [ - app: insert(:oauth_app, redirect_uris: "https://redirect.url"), + app: insert(:oauth_app, redirect_uris: ["https://redirect.url"]), conn: build_conn() |> Plug.Session.call(Plug.Session.init(@session_opts)) From 05704ec86e605ca681c5e95eac57b53851721548 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Wed, 14 Jan 2026 02:37:55 +0100 Subject: [PATCH 31/40] mix: upgrade vix from "~> 0.26.0" to "~> 0.36" Dropping the last zero should allow to get 0.x updates rather than only 0.36.x updates. Fixes: https://git.pleroma.social/pleroma/pleroma/-/issues/3393 --- changelog.d/vix-0.36.0.fix | 1 + mix.exs | 2 +- mix.lock | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 changelog.d/vix-0.36.0.fix diff --git a/changelog.d/vix-0.36.0.fix b/changelog.d/vix-0.36.0.fix new file mode 100644 index 000000000..43a8dd8f8 --- /dev/null +++ b/changelog.d/vix-0.36.0.fix @@ -0,0 +1 @@ +Fix compilation with vips-8.18.0 with bumping to vix 0.36.0 diff --git a/mix.exs b/mix.exs index 48ec9b68f..5b0c62a49 100644 --- a/mix.exs +++ b/mix.exs @@ -193,7 +193,7 @@ defmodule Pleroma.Mixfile do {:majic, "~> 1.0"}, {:open_api_spex, "~> 3.16"}, {:ecto_psql_extras, "~> 0.8"}, - {:vix, "~> 0.26.0"}, + {:vix, "~> 0.36"}, {:elixir_make, "~> 0.7.7", override: true}, {:blurhash, "~> 0.1.0", hex: :rinpatch_blurhash}, {:exile, "~> 0.10.0"}, diff --git a/mix.lock b/mix.lock index c469f4f01..a0ef38fc0 100644 --- a/mix.lock +++ b/mix.lock @@ -152,7 +152,7 @@ "ueberauth": {:hex, :ueberauth, "0.10.8", "ba78fbcbb27d811a6cd06ad851793aaf7d27c3b30c9e95349c2c362b344cd8f0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f2d3172e52821375bccb8460e5fa5cb91cfd60b19b636b6e57e9759b6f8c10c1"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"}, "unsafe": {:hex, :unsafe, "1.0.2", "23c6be12f6c1605364801f4b47007c0c159497d0446ad378b5cf05f1855c0581", [:mix], [], "hexpm", "b485231683c3ab01a9cd44cb4a79f152c6f3bb87358439c6f68791b85c2df675"}, - "vix": {:hex, :vix, "0.26.0", "027f10b6969b759318be84bd0bd8c88af877445e4e41cf96a0460392cea5399c", [:make, :mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:cc_precompiler, "~> 0.1.4 or ~> 0.2", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.7.3 or ~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}], "hexpm", "71b0a79ae7f199cacfc8e679b0e4ba25ee47dc02e182c5b9097efb29fbe14efd"}, + "vix": {:hex, :vix, "0.36.0", "3132dc065beda06dab1895a53d8c852d8e6a5bbca375c609435e968b1290e113", [:make, :mix], [{:cc_precompiler, "~> 0.1.4 or ~> 0.2", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.7.3 or ~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}], "hexpm", "92f912b4e90c453f92942742105bcdb367ad53406759da251bd2e587e33f4134"}, "web_push_encryption": {:hex, :web_push_encryption, "0.3.1", "76d0e7375142dfee67391e7690e89f92578889cbcf2879377900b5620ee4708d", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.1", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "4f82b2e57622fb9337559058e8797cb0df7e7c9790793bdc4e40bc895f70e2a2"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, From 4df7f93a23d5b7c9d6d5c78695bd6974e63158ea Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Fri, 16 Jan 2026 11:25:39 +0400 Subject: [PATCH 32/40] Fix OAuth registration redirect_uris array support --- .../oauth-registration-redirect_uris.fix | 1 + .../operations/admin/o_auth_app_operation.ex | 12 ++++++--- .../web/api_spec/operations/app_operation.ex | 5 +++- lib/pleroma/web/o_auth/app.ex | 25 ++++++++++++++++- .../o_auth_app_controller_test.exs | 22 +++++++++++++++ .../controllers/app_controller_test.exs | 27 +++++++++++++++++++ test/pleroma/web/o_auth/app_test.exs | 14 +++++----- .../web/o_auth/o_auth_controller_test.exs | 2 +- 8 files changed, 94 insertions(+), 14 deletions(-) diff --git a/changelog.d/oauth-registration-redirect_uris.fix b/changelog.d/oauth-registration-redirect_uris.fix index e69de29bb..76ace55df 100644 --- a/changelog.d/oauth-registration-redirect_uris.fix +++ b/changelog.d/oauth-registration-redirect_uris.fix @@ -0,0 +1 @@ +Fix OAuth app registration to accept `redirect_uris` as an array of strings (RFC 7591), while keeping backwards compatibility with string input. diff --git a/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex b/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex index 5d4306754..7d83066ca 100644 --- a/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex @@ -123,8 +123,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do name: %Schema{type: :string, description: "Application Name"}, scopes: %Schema{type: :array, items: %Schema{type: :string}, description: "oAuth scopes"}, redirect_uris: %Schema{ - type: :array, - items: %Schema{type: :string}, + oneOf: [ + %Schema{type: :string}, + %Schema{type: :array, items: %Schema{type: :string}} + ], description: "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter." }, @@ -158,8 +160,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do name: %Schema{type: :string, description: "Application Name"}, scopes: %Schema{type: :array, items: %Schema{type: :string}, description: "oAuth scopes"}, redirect_uris: %Schema{ - type: :array, - items: %Schema{type: :string}, + oneOf: [ + %Schema{type: :string}, + %Schema{type: :array, items: %Schema{type: :string}} + ], description: "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter." }, diff --git a/lib/pleroma/web/api_spec/operations/app_operation.ex b/lib/pleroma/web/api_spec/operations/app_operation.ex index dfa2237c0..71c15a665 100644 --- a/lib/pleroma/web/api_spec/operations/app_operation.ex +++ b/lib/pleroma/web/api_spec/operations/app_operation.ex @@ -97,7 +97,10 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do properties: %{ client_name: %Schema{type: :string, description: "A name for your application."}, redirect_uris: %Schema{ - type: :string, + oneOf: [ + %Schema{type: :string}, + %Schema{type: :array, items: %Schema{type: :string}} + ], description: "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter." }, diff --git a/lib/pleroma/web/o_auth/app.ex b/lib/pleroma/web/o_auth/app.ex index f1145d500..a2841b2bb 100644 --- a/lib/pleroma/web/o_auth/app.ex +++ b/lib/pleroma/web/o_auth/app.ex @@ -14,7 +14,7 @@ defmodule Pleroma.Web.OAuth.App do schema "apps" do field(:client_name, :string) - field(:redirect_uris, {:array, :string}) + field(:redirect_uris, :string) field(:scopes, {:array, :string}, default: []) field(:website, :string) field(:client_id, :string) @@ -31,9 +31,32 @@ defmodule Pleroma.Web.OAuth.App do @spec changeset(t(), map()) :: Ecto.Changeset.t() def changeset(struct, params) do + params = normalize_redirect_uris_param(params) + cast(struct, params, [:client_name, :redirect_uris, :scopes, :website, :trusted, :user_id]) end + defp normalize_redirect_uris_param(%{} = params) do + case params do + %{redirect_uris: redirect_uris} when is_list(redirect_uris) -> + Map.put(params, :redirect_uris, normalize_redirect_uris(redirect_uris)) + + %{"redirect_uris" => redirect_uris} when is_list(redirect_uris) -> + Map.put(params, "redirect_uris", normalize_redirect_uris(redirect_uris)) + + _ -> + params + end + end + + defp normalize_redirect_uris(redirect_uris) when is_list(redirect_uris) do + redirect_uris + |> Enum.filter(&is_binary/1) + |> Enum.map(&String.trim/1) + |> Enum.reject(&(&1 == "")) + |> Enum.join("\n") + end + @spec register_changeset(t(), map()) :: Ecto.Changeset.t() def register_changeset(struct, params \\ %{}) do changeset = diff --git a/test/pleroma/web/admin_api/controllers/o_auth_app_controller_test.exs b/test/pleroma/web/admin_api/controllers/o_auth_app_controller_test.exs index 10eefbeca..2c2d13bb7 100644 --- a/test/pleroma/web/admin_api/controllers/o_auth_app_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/o_auth_app_controller_test.exs @@ -57,6 +57,28 @@ defmodule Pleroma.Web.AdminAPI.OAuthAppControllerTest do } = response end + test "success with redirect_uris array", %{conn: conn} do + base_url = Endpoint.url() + app_name = "Trusted app" + + response = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/admin/oauth_app", %{ + name: app_name, + redirect_uris: [base_url] + }) + |> json_response_and_validate_schema(200) + + assert %{ + "client_id" => _, + "client_secret" => _, + "name" => ^app_name, + "redirect_uri" => ^base_url, + "trusted" => false + } = response + end + test "with trusted", %{conn: conn} do base_url = Endpoint.url() app_name = "Trusted app" diff --git a/test/pleroma/web/mastodon_api/controllers/app_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/app_controller_test.exs index bc9d4048c..45902d7d9 100644 --- a/test/pleroma/web/mastodon_api/controllers/app_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/app_controller_test.exs @@ -61,6 +61,33 @@ defmodule Pleroma.Web.MastodonAPI.AppControllerTest do assert app.user_id == nil end + test "creates an oauth app with redirect_uris array", %{conn: conn} do + app_attrs = build(:oauth_app) + + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/apps", %{ + client_name: app_attrs.client_name, + redirect_uris: [app_attrs.redirect_uris] + }) + + [app] = Repo.all(App) + + expected = %{ + "name" => app.client_name, + "website" => app.website, + "client_id" => app.client_id, + "client_secret" => app.client_secret, + "id" => app.id |> to_string(), + "redirect_uri" => app.redirect_uris, + "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key) + } + + assert expected == json_response_and_validate_schema(conn, 200) + assert app.user_id == nil + end + test "creates an oauth app with a user", %{conn: conn} do user = insert(:user) app_attrs = build(:oauth_app) diff --git a/test/pleroma/web/o_auth/app_test.exs b/test/pleroma/web/o_auth/app_test.exs index f90bee358..a69ba371e 100644 --- a/test/pleroma/web/o_auth/app_test.exs +++ b/test/pleroma/web/o_auth/app_test.exs @@ -10,20 +10,20 @@ defmodule Pleroma.Web.OAuth.AppTest do describe "get_or_make/2" do test "gets exist app" do - attrs = %{client_name: "Mastodon-Local", redirect_uris: ["."]} + attrs = %{client_name: "Mastodon-Local", redirect_uris: "."} app = insert(:oauth_app, Map.merge(attrs, %{scopes: ["read", "write"]})) {:ok, %App{} = exist_app} = App.get_or_make(attrs, []) assert exist_app == app end test "make app" do - attrs = %{client_name: "Mastodon-Local", redirect_uris: ["."]} + attrs = %{client_name: "Mastodon-Local", redirect_uris: "."} {:ok, %App{} = app} = App.get_or_make(attrs, ["write"]) assert app.scopes == ["write"] end test "gets exist app and updates scopes" do - attrs = %{client_name: "Mastodon-Local", redirect_uris: ["."]} + attrs = %{client_name: "Mastodon-Local", redirect_uris: "."} app = insert(:oauth_app, Map.merge(attrs, %{scopes: ["read", "write"]})) {:ok, %App{} = exist_app} = App.get_or_make(attrs, ["read", "write", "follow", "push"]) assert exist_app.id == app.id @@ -31,10 +31,10 @@ defmodule Pleroma.Web.OAuth.AppTest do end test "has unique client_id" do - insert(:oauth_app, client_name: "", redirect_uris: [""], client_id: "boop") + insert(:oauth_app, client_name: "", redirect_uris: "", client_id: "boop") error = - catch_error(insert(:oauth_app, client_name: "", redirect_uris: [""], client_id: "boop")) + catch_error(insert(:oauth_app, client_name: "", redirect_uris: "", client_id: "boop")) assert %Ecto.ConstraintError{} = error assert error.constraint == "apps_client_id_index" @@ -55,7 +55,7 @@ defmodule Pleroma.Web.OAuth.AppTest do end test "removes orphaned apps" do - attrs = %{client_name: "Mastodon-Local", redirect_uris: ["."]} + attrs = %{client_name: "Mastodon-Local", redirect_uris: "."} {:ok, %App{} = old_app} = App.get_or_make(attrs, ["write"]) # backdate the old app so it's within the threshold for being cleaned up @@ -66,7 +66,7 @@ defmodule Pleroma.Web.OAuth.AppTest do |> Pleroma.Repo.query([one_hour_ago, old_app.id]) # Create the new app after backdating the old one - attrs = %{client_name: "PleromaFE", redirect_uris: ["."]} + attrs = %{client_name: "PleromaFE", redirect_uris: "."} {:ok, %App{} = app} = App.get_or_make(attrs, ["write"]) # Ensure the new app has a recent timestamp diff --git a/test/pleroma/web/o_auth/o_auth_controller_test.exs b/test/pleroma/web/o_auth/o_auth_controller_test.exs index 3788b9c65..260442771 100644 --- a/test/pleroma/web/o_auth/o_auth_controller_test.exs +++ b/test/pleroma/web/o_auth/o_auth_controller_test.exs @@ -406,7 +406,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do describe "GET /oauth/authorize" do setup do [ - app: insert(:oauth_app, redirect_uris: ["https://redirect.url"]), + app: insert(:oauth_app, redirect_uris: "https://redirect.url"), conn: build_conn() |> Plug.Session.call(Plug.Session.init(@session_opts)) From e91bb2144d9916a477bfc01c4f64c9938ba03e46 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Fri, 16 Jan 2026 16:17:21 +0400 Subject: [PATCH 33/40] InstanceView: Omit comment if it's empty --- .../web/mastodon_api/views/instance_view.ex | 11 ++++++++--- .../controllers/instance_controller_test.exs | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 18c6c0f80..0dc7a5fea 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -123,12 +123,17 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do host in exclusions or not Map.has_key?(@block_severities, rule) end) |> Enum.map(fn {host, reason} -> - %{ + domain_block = %{ domain: host, digest: :crypto.hash(:sha256, host) |> Base.encode16(case: :lower), - severity: Map.get(@block_severities, rule), - comment: reason + severity: Map.get(@block_severities, rule) } + + if not_empty_string(reason) do + Map.put(domain_block, :comment, reason) + else + domain_block + end end) end) |> List.flatten() diff --git a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs index e0c9091e0..461b46066 100644 --- a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs @@ -171,6 +171,22 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do ] == json_response_and_validate_schema(conn, 200) end + test "omits comment field if comment is empty", %{conn: conn} do + clear_config([:mrf_simple, :reject], ["fediverse.pl"]) + + conn = get(conn, "/api/v1/instance/domain_blocks") + + assert [ + %{ + "digest" => "55e3f44aefe7eb022d3b1daaf7396cabf7f181bf6093c8ea841e30c9fc7d8226", + "domain" => "fediverse.pl", + "severity" => "suspend" + } = domain_block + ] = json_response_and_validate_schema(conn, 200) + + refute Map.has_key?(domain_block, "comment") + end + test "returns empty array if mrf transparency is disabled", %{conn: conn} do clear_config([:mrf, :transparency], false) From ef0f04ca48e8b7e848ef5fd7cac9d9e074248d59 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Fri, 16 Jan 2026 21:16:04 +0400 Subject: [PATCH 34/40] http(hackney): disable adapter redirects by default Hackney 1.25.x has redirect handling issues behind CONNECT proxies and with pools. Disable hackney-level redirects and rely on Tesla.Middleware.FollowRedirects instead. Also default to with_body: true so redirects can be followed reliably. --- lib/pleroma/http/adapter_helper/hackney.ex | 5 +++-- test/pleroma/http/adapter_helper/hackney_test.exs | 8 ++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/http/adapter_helper/hackney.ex b/lib/pleroma/http/adapter_helper/hackney.ex index f3451cf9c..2b1502001 100644 --- a/lib/pleroma/http/adapter_helper/hackney.ex +++ b/lib/pleroma/http/adapter_helper/hackney.ex @@ -6,8 +6,9 @@ defmodule Pleroma.HTTP.AdapterHelper.Hackney do @behaviour Pleroma.HTTP.AdapterHelper @defaults [ - follow_redirect: true, - force_redirect: true + follow_redirect: false, + force_redirect: false, + with_body: true ] @spec options(keyword(), URI.t()) :: keyword() diff --git a/test/pleroma/http/adapter_helper/hackney_test.exs b/test/pleroma/http/adapter_helper/hackney_test.exs index 57ce4728c..343bdb800 100644 --- a/test/pleroma/http/adapter_helper/hackney_test.exs +++ b/test/pleroma/http/adapter_helper/hackney_test.exs @@ -16,6 +16,14 @@ defmodule Pleroma.HTTP.AdapterHelper.HackneyTest do describe "options/2" do setup do: clear_config([:http, :adapter], a: 1, b: 2) + test "uses redirect-safe defaults", %{uri: uri} do + opts = Hackney.options([], uri) + + assert opts[:follow_redirect] == false + assert opts[:force_redirect] == false + assert opts[:with_body] == true + end + test "add proxy and opts from config", %{uri: uri} do opts = Hackney.options([proxy: "localhost:8123"], uri) From 52fc344b0a8c8ebe3496fb3d7bc7ae026377dbd2 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Fri, 16 Jan 2026 21:16:26 +0400 Subject: [PATCH 35/40] test(http): cover pooled redirect with hackney Reproduces the Hackney 1.25 pooled redirect cleanup issue which can surface as :req_not_found when the adapter returns a Ref and the body is later fetched. --- .../http/hackney_redirect_regression_test.exs | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 test/pleroma/http/hackney_redirect_regression_test.exs diff --git a/test/pleroma/http/hackney_redirect_regression_test.exs b/test/pleroma/http/hackney_redirect_regression_test.exs new file mode 100644 index 000000000..61389aa7e --- /dev/null +++ b/test/pleroma/http/hackney_redirect_regression_test.exs @@ -0,0 +1,151 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.HTTP.HackneyRedirectRegressionTest do + use ExUnit.Case, async: false + + alias Pleroma.HTTP.AdapterHelper.Hackney, as: HackneyAdapterHelper + + setup do + {:ok, _} = Application.ensure_all_started(:hackney) + + {:ok, server} = start_server() + on_exit(fn -> stop_server(server) end) + + {:ok, server: server} + end + + test "pooled redirects work with follow_redirect disabled", %{server: server} do + url = "#{server.base_url}/redirect" + uri = URI.parse(url) + + adapter_opts = + HackneyAdapterHelper.options( + [pool: :media, follow_redirect: false, no_proxy_env: true], + uri + ) + + client = Tesla.client([Tesla.Middleware.FollowRedirects], Tesla.Adapter.Hackney) + + assert {:ok, %Tesla.Env{status: 200, body: "ok"}} = + Tesla.request(client, method: :get, url: url, opts: [adapter: adapter_opts]) + end + + defp start_server do + {:ok, listener} = + :gen_tcp.listen(0, [ + :binary, + active: false, + packet: :raw, + reuseaddr: true, + ip: {127, 0, 0, 1} + ]) + + {:ok, {{127, 0, 0, 1}, port}} = :inet.sockname(listener) + + {:ok, acceptor} = + Task.start_link(fn -> + accept_loop(listener) + end) + + {:ok, %{listener: listener, acceptor: acceptor, base_url: "http://127.0.0.1:#{port}"}} + end + + defp stop_server(%{listener: listener, acceptor: acceptor}) do + :ok = :gen_tcp.close(listener) + + if Process.alive?(acceptor) do + Process.exit(acceptor, :normal) + end + end + + defp accept_loop(listener) do + case :gen_tcp.accept(listener) do + {:ok, socket} -> + serve(socket) + accept_loop(listener) + + {:error, :closed} -> + :ok + + {:error, _reason} -> + :ok + end + end + + defp serve(socket) do + with {:ok, data} <- recv_headers(socket), + {:ok, path} <- parse_path(data) do + case path do + "/redirect" -> + send_response(socket, 302, "Found", [{"Location", "/final"}], "") + + "/final" -> + send_response(socket, 200, "OK", [], "ok") + + _ -> + send_response(socket, 404, "Not Found", [], "not found") + end + else + _ -> :ok + end + + :gen_tcp.close(socket) + end + + defp recv_headers(socket, acc \\ <<>>) do + case :gen_tcp.recv(socket, 0, 1_000) do + {:ok, data} -> + acc = acc <> data + + if :binary.match(acc, "\r\n\r\n") != :nomatch do + {:ok, acc} + else + if byte_size(acc) > 8_192 do + {:error, :too_large} + else + recv_headers(socket, acc) + end + end + + {:error, _} = error -> + error + end + end + + defp parse_path(data) do + case String.split(data, "\r\n", parts: 2) do + [request_line | _] -> + case String.split(request_line, " ") do + [_method, path, _protocol] -> {:ok, path} + _ -> {:error, :invalid_request} + end + + _ -> + {:error, :invalid_request} + end + end + + defp send_response(socket, status, reason, headers, body) do + base_headers = + [ + {"Content-Length", Integer.to_string(byte_size(body))}, + {"Connection", "close"} + ] ++ headers + + iodata = + [ + "HTTP/1.1 ", + Integer.to_string(status), + " ", + reason, + "\r\n", + Enum.map(base_headers, fn {k, v} -> [k, ": ", v, "\r\n"] end), + "\r\n", + body + ] + + :gen_tcp.send(socket, iodata) + end +end From e67b4cd8b2b8beded29880778d8e044ec0a4fc21 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Fri, 16 Jan 2026 21:16:39 +0400 Subject: [PATCH 36/40] test(http): reproduce hackney follow_redirect crash via CONNECT proxy Hackney 1.25 crashes when follow_redirect is enabled behind an HTTPS CONNECT proxy and the Location header is relative (hackney_http_connect transport). This test demonstrates the failure and verifies Tesla-level redirects work when hackney redirects are disabled. --- test/fixtures/server.pem | 18 + ...ackney_follow_redirect_regression_test.exs | 325 ++++++++++++++++++ 2 files changed, 343 insertions(+) create mode 100644 test/fixtures/server.pem create mode 100644 test/pleroma/http/hackney_follow_redirect_regression_test.exs diff --git a/test/fixtures/server.pem b/test/fixtures/server.pem new file mode 100644 index 000000000..8bd3d6d05 --- /dev/null +++ b/test/fixtures/server.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIICpDCCAYwCCQC0vCQAnSoGdzANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAls +b2NhbGhvc3QwHhcNMjYwMTE2MTY1ODE5WhcNMzYwMTE0MTY1ODE5WjAUMRIwEAYD +VQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCq +dZ4O2upZqwIo1eK5KrW1IIsjkfsFK8hE7Llh+4axcesiUKot0ib1CUhRSYiL1DLO +CIYQOw8IKQDVSC4JWAX9SsnX4W8dwexMQuSQG7/IKX2auC1bNNySFvoqM6Gq3GL9 +MqBFonZGXDPZu8fmxsI/2p9+2GK13F+HXgoLlXSCoO3XELJaBmjv29tgxxWRxCiH +m4u0briSxgUEx+CctpKPvGDmLaoIOIhjtuoG6OjkeWUOp6jDcteazO23VxPyF5cS +NbRJgm8AckrTQ6wbWSnhyqF8rPEsIc0ZAlUdDEs5fL3sjugc566FvE+GOkZIEyDD +tgWbc4Ne+Kp/nnt6oVxpAgMBAAEwDQYJKoZIhvcNAQELBQADggEBADv+J1DTok8V +MKVKo0hsRnHTeJQ2+EIgOspuYlEzez3PysOZH6diAQxO2lzuo9LKxP3hnmw17XO/ +P2oCzYyb9/P58VY/gr4UDIfuhgcE0cVfdsRhVId/I2FW6VP2f5q1TGbDUxSsVIlG +6hufn1aLBu90LtEbDkHqbnD05yYPwdqzWg4TrOXbX+jBhQrXJJdB3W7KTgozjRQw +F7+/2IyXoxXuxcwQBQlYhUbvGlsFqFpP/6cz2al5i5pNUkiNaSYwlRmuwa7zoTft +tHf57dhfXIpXET2BaJM6DSjDOOG/QleRXkvkTI5J21q+Bo+XnOzo19p4cZKJpTFC +SNgrftyNh3k= +-----END CERTIFICATE----- + diff --git a/test/pleroma/http/hackney_follow_redirect_regression_test.exs b/test/pleroma/http/hackney_follow_redirect_regression_test.exs new file mode 100644 index 000000000..ea631024c --- /dev/null +++ b/test/pleroma/http/hackney_follow_redirect_regression_test.exs @@ -0,0 +1,325 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.HTTP.HackneyFollowRedirectRegressionTest do + use ExUnit.Case, async: false + + setup do + {:ok, _} = Application.ensure_all_started(:hackney) + + {:ok, tls_server} = start_tls_redirect_server() + {:ok, proxy} = start_connect_proxy() + + on_exit(fn -> + stop_connect_proxy(proxy) + stop_tls_redirect_server(tls_server) + end) + + {:ok, tls_server: tls_server, proxy: proxy} + end + + test "hackney follow_redirect crashes behind CONNECT proxy on relative redirects", %{ + tls_server: tls_server, + proxy: proxy + } do + url = "#{tls_server.base_url}/redirect" + + opts = [ + pool: :media, + proxy: proxy.proxy_url, + insecure: true, + connect_timeout: 1_000, + recv_timeout: 1_000, + follow_redirect: true, + force_redirect: true + ] + + {pid, ref} = spawn_monitor(fn -> :hackney.request(:get, url, [], <<>>, opts) end) + + assert_receive {:DOWN, ^ref, :process, ^pid, reason}, 5_000 + + assert match?({%FunctionClauseError{}, _}, reason) or match?(%FunctionClauseError{}, reason) or + match?({:function_clause, _}, reason) + end + + test "redirects work via proxy when hackney follow_redirect is disabled", %{ + tls_server: tls_server, + proxy: proxy + } do + url = "#{tls_server.base_url}/redirect" + + adapter_opts = [ + pool: :media, + proxy: proxy.proxy_url, + insecure: true, + connect_timeout: 1_000, + recv_timeout: 1_000, + follow_redirect: false, + force_redirect: false, + with_body: true + ] + + client = Tesla.client([Tesla.Middleware.FollowRedirects], Tesla.Adapter.Hackney) + + assert {:ok, %Tesla.Env{status: 200, body: "ok"}} = + Tesla.request(client, method: :get, url: url, opts: [adapter: adapter_opts]) + end + + defp start_tls_redirect_server do + certfile = Path.expand("../../fixtures/server.pem", __DIR__) + keyfile = Path.expand("../../fixtures/private_key.pem", __DIR__) + + {:ok, listener} = + :ssl.listen(0, [ + :binary, + certfile: certfile, + keyfile: keyfile, + reuseaddr: true, + active: false, + packet: :raw, + ip: {127, 0, 0, 1} + ]) + + {:ok, {{127, 0, 0, 1}, port}} = :ssl.sockname(listener) + + {:ok, acceptor} = + Task.start_link(fn -> + accept_tls_loop(listener) + end) + + {:ok, %{listener: listener, acceptor: acceptor, base_url: "https://127.0.0.1:#{port}"}} + end + + defp stop_tls_redirect_server(%{listener: listener, acceptor: acceptor}) do + :ok = :ssl.close(listener) + + if Process.alive?(acceptor) do + Process.exit(acceptor, :normal) + end + end + + defp accept_tls_loop(listener) do + case :ssl.transport_accept(listener) do + {:ok, socket} -> + _ = Task.start(fn -> serve_tls(socket) end) + accept_tls_loop(listener) + + {:error, :closed} -> + :ok + + {:error, _reason} -> + :ok + end + end + + defp serve_tls(tcp_socket) do + with {:ok, ssl_socket} <- :ssl.handshake(tcp_socket, 2_000), + {:ok, data} <- recv_ssl_headers(ssl_socket), + {:ok, path} <- parse_path(data) do + case path do + "/redirect" -> + send_ssl_response(ssl_socket, 302, "Found", [{"Location", "/final"}], "") + + "/final" -> + send_ssl_response(ssl_socket, 200, "OK", [], "ok") + + _ -> + send_ssl_response(ssl_socket, 404, "Not Found", [], "not found") + end + + :ssl.close(ssl_socket) + else + _ -> + _ = :gen_tcp.close(tcp_socket) + :ok + end + end + + defp recv_ssl_headers(socket, acc \\ <<>>) do + case :ssl.recv(socket, 0, 1_000) do + {:ok, data} -> + acc = acc <> data + + if :binary.match(acc, "\r\n\r\n") != :nomatch do + {:ok, acc} + else + if byte_size(acc) > 8_192 do + {:error, :too_large} + else + recv_ssl_headers(socket, acc) + end + end + + {:error, _} = error -> + error + end + end + + defp send_ssl_response(socket, status, reason, headers, body) do + base_headers = + [ + {"Content-Length", Integer.to_string(byte_size(body))}, + {"Connection", "close"} + ] ++ headers + + iodata = + [ + "HTTP/1.1 ", + Integer.to_string(status), + " ", + reason, + "\r\n", + Enum.map(base_headers, fn {k, v} -> [k, ": ", v, "\r\n"] end), + "\r\n", + body + ] + + :ssl.send(socket, iodata) + end + + defp start_connect_proxy do + {:ok, listener} = + :gen_tcp.listen(0, [ + :binary, + active: false, + packet: :raw, + reuseaddr: true, + ip: {127, 0, 0, 1} + ]) + + {:ok, {{127, 0, 0, 1}, port}} = :inet.sockname(listener) + + {:ok, acceptor} = + Task.start_link(fn -> + accept_proxy_loop(listener) + end) + + {:ok, %{listener: listener, acceptor: acceptor, proxy_url: "127.0.0.1:#{port}"}} + end + + defp stop_connect_proxy(%{listener: listener, acceptor: acceptor}) do + :ok = :gen_tcp.close(listener) + + if Process.alive?(acceptor) do + Process.exit(acceptor, :normal) + end + end + + defp accept_proxy_loop(listener) do + case :gen_tcp.accept(listener) do + {:ok, socket} -> + _ = Task.start(fn -> serve_proxy(socket) end) + accept_proxy_loop(listener) + + {:error, :closed} -> + :ok + + {:error, _reason} -> + :ok + end + end + + defp serve_proxy(client_socket) do + with {:ok, {headers, rest}} <- recv_tcp_headers(client_socket), + {:ok, {host, port}} <- parse_connect(headers), + {:ok, upstream_socket} <- connect_upstream(host, port) do + :gen_tcp.send(client_socket, "HTTP/1.1 200 Connection established\r\n\r\n") + + if rest != <<>> do + :gen_tcp.send(upstream_socket, rest) + end + + tunnel(client_socket, upstream_socket) + else + _ -> + :gen_tcp.close(client_socket) + :ok + end + end + + defp tunnel(client_socket, upstream_socket) do + parent = self() + _ = spawn_link(fn -> forward(client_socket, upstream_socket, parent) end) + _ = spawn_link(fn -> forward(upstream_socket, client_socket, parent) end) + + receive do + :tunnel_closed -> :ok + after + 10_000 -> :ok + end + + :gen_tcp.close(client_socket) + :gen_tcp.close(upstream_socket) + end + + defp forward(from_socket, to_socket, parent) do + case :gen_tcp.recv(from_socket, 0, 10_000) do + {:ok, data} -> + _ = :gen_tcp.send(to_socket, data) + forward(from_socket, to_socket, parent) + + {:error, _reason} -> + send(parent, :tunnel_closed) + :ok + end + end + + defp recv_tcp_headers(socket, acc \\ <<>>) do + case :gen_tcp.recv(socket, 0, 1_000) do + {:ok, data} -> + acc = acc <> data + + case :binary.match(acc, "\r\n\r\n") do + :nomatch -> + if byte_size(acc) > 8_192 do + {:error, :too_large} + else + recv_tcp_headers(socket, acc) + end + + {idx, _len} -> + split_at = idx + 4 + <> = acc + {:ok, {headers, rest}} + end + + {:error, _} = error -> + error + end + end + + defp parse_connect(data) do + with [request_line | _] <- String.split(data, "\r\n", trim: true), + ["CONNECT", hostport | _] <- String.split(request_line, " ", parts: 3), + [host, port_str] <- String.split(hostport, ":", parts: 2), + {port, ""} <- Integer.parse(port_str) do + {:ok, {host, port}} + else + _ -> {:error, :invalid_connect} + end + end + + defp connect_upstream(host, port) do + address = + case :inet.parse_address(String.to_charlist(host)) do + {:ok, ip} -> ip + {:error, _} -> String.to_charlist(host) + end + + :gen_tcp.connect(address, port, [:binary, active: false, packet: :raw], 1_000) + end + + defp parse_path(data) do + case String.split(data, "\r\n", parts: 2) do + [request_line | _] -> + case String.split(request_line, " ") do + [_method, path, _protocol] -> {:ok, path} + _ -> {:error, :invalid_request} + end + + _ -> + {:error, :invalid_request} + end + end +end From 9b1941366f08d3046abd8c943e726f4e29c81e4c Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 24 Dec 2025 14:35:54 -0800 Subject: [PATCH 37/40] In-house redirect handler for mediaproxy with Hackney adapter Also ensure we always pass an absolute URL to Hackney when parsing a redirect response (cherry picked from commit 00ac6bce8d244eec7e2460358296619e5cacba6b) --- changelog.d/hackney-mediaproxy.change | 1 + lib/pleroma/reverse_proxy/client/hackney.ex | 44 ++++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 changelog.d/hackney-mediaproxy.change diff --git a/changelog.d/hackney-mediaproxy.change b/changelog.d/hackney-mediaproxy.change new file mode 100644 index 000000000..10dfb0775 --- /dev/null +++ b/changelog.d/hackney-mediaproxy.change @@ -0,0 +1 @@ +Use a custom redirect handler to ensure MediaProxy redirects are followed with Hackney diff --git a/lib/pleroma/reverse_proxy/client/hackney.ex b/lib/pleroma/reverse_proxy/client/hackney.ex index 0aa5f5715..7ccd28bb1 100644 --- a/lib/pleroma/reverse_proxy/client/hackney.ex +++ b/lib/pleroma/reverse_proxy/client/hackney.ex @@ -5,6 +5,20 @@ defmodule Pleroma.ReverseProxy.Client.Hackney do @behaviour Pleroma.ReverseProxy.Client + # redirect handler from Pleb, slightly modified to work with Hackney + # https://declin.eu/objects/d4f38e62-5429-4614-86d1-e8fc16e6bf33 + # https://github.com/benoitc/hackney/issues/273 + @redirect_statuses [301, 302, 303, 307, 308] + defp absolute_redirect_url(original_url, resp_headers) do + location = + Enum.find(resp_headers, fn {header, _location} -> + String.downcase(header) == "location" + end) + + URI.merge(original_url, elem(location, 1)) + |> URI.to_string() + end + @impl true def request(method, url, headers, body, opts \\ []) do opts = @@ -12,7 +26,35 @@ defmodule Pleroma.ReverseProxy.Client.Hackney do path end) - :hackney.request(method, url, headers, body, opts) + if opts[:follow_redirect] != false do + {_state, req_opts} = Access.get_and_update(opts, :follow_redirect, fn a -> {a, false} end) + res = :hackney.request(method, url, headers, body, req_opts) + + case res do + {:ok, code, resp_headers, _client} when code in @redirect_statuses -> + :hackney.request( + method, + absolute_redirect_url(url, resp_headers), + headers, + body, + req_opts + ) + + {:ok, code, resp_headers} when code in @redirect_statuses -> + :hackney.request( + method, + absolute_redirect_url(url, resp_headers), + headers, + body, + req_opts + ) + + _ -> + res + end + else + :hackney.request(method, url, headers, body, opts) + end end @impl true From 1a6a8f6fb4e001885d7163d6840e3fa1d7c09a78 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Fri, 16 Jan 2026 22:00:36 +0400 Subject: [PATCH 38/40] test(http): cover reverse proxy redirects via CONNECT proxy Exercises Pleroma.ReverseProxy.Client.Hackney with follow_redirect enabled behind an HTTPS CONNECT proxy, ensuring the client follows a relative redirect and can stream the final body. --- ...ackney_follow_redirect_regression_test.exs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/pleroma/http/hackney_follow_redirect_regression_test.exs b/test/pleroma/http/hackney_follow_redirect_regression_test.exs index ea631024c..71fda4479 100644 --- a/test/pleroma/http/hackney_follow_redirect_regression_test.exs +++ b/test/pleroma/http/hackney_follow_redirect_regression_test.exs @@ -66,6 +66,36 @@ defmodule Pleroma.HTTP.HackneyFollowRedirectRegressionTest do Tesla.request(client, method: :get, url: url, opts: [adapter: adapter_opts]) end + test "reverse proxy hackney client follows redirects via proxy without crashing", %{ + tls_server: tls_server, + proxy: proxy + } do + url = "#{tls_server.base_url}/redirect" + + opts = [ + pool: :media, + proxy: proxy.proxy_url, + insecure: true, + connect_timeout: 1_000, + recv_timeout: 1_000, + follow_redirect: true + ] + + assert {:ok, 200, _headers, ref} = + Pleroma.ReverseProxy.Client.Hackney.request(:get, url, [], "", opts) + + assert collect_body(ref) == "ok" + Pleroma.ReverseProxy.Client.Hackney.close(ref) + end + + defp collect_body(ref, acc \\ "") do + case Pleroma.ReverseProxy.Client.Hackney.stream_body(ref) do + :done -> acc + {:ok, data, _ref} -> collect_body(ref, acc <> data) + {:error, error} -> flunk("stream_body failed: #{inspect(error)}") + end + end + defp start_tls_redirect_server do certfile = Path.expand("../../fixtures/server.pem", __DIR__) keyfile = Path.expand("../../fixtures/private_key.pem", __DIR__) From e7d2d9bd89bd006d094015711e5b8a220cd490d1 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Sat, 17 Jan 2026 02:24:07 +0400 Subject: [PATCH 39/40] mrf(media_proxy_warming): avoid adapter-level redirects Drop follow_redirect/force_redirect from the HTTP options used when warming MediaProxy, relying on Tesla middleware instead (Hackney redirect handling can crash behind CONNECT proxies). Also add a regression assertion in the policy test and document the upstream Hackney issues in ReverseProxy redirect handling. --- lib/pleroma/reverse_proxy/client/hackney.ex | 7 +++++-- .../mrf/media_proxy_warming_policy.ex | 9 +++++++- .../mrf/media_proxy_warming_policy_test.exs | 21 +++++++------------ 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/lib/pleroma/reverse_proxy/client/hackney.ex b/lib/pleroma/reverse_proxy/client/hackney.ex index 7ccd28bb1..7e1fca80d 100644 --- a/lib/pleroma/reverse_proxy/client/hackney.ex +++ b/lib/pleroma/reverse_proxy/client/hackney.ex @@ -5,9 +5,12 @@ defmodule Pleroma.ReverseProxy.Client.Hackney do @behaviour Pleroma.ReverseProxy.Client - # redirect handler from Pleb, slightly modified to work with Hackney + # In-app redirect handler to avoid Hackney redirect bugs: + # - https://github.com/benoitc/hackney/issues/527 (relative/protocol-less redirects can crash Hackney) + # - https://github.com/benoitc/hackney/issues/273 (redirects not followed when using HTTP proxy) + # + # Based on a redirect handler from Pleb, slightly modified to work with Hackney: # https://declin.eu/objects/d4f38e62-5429-4614-86d1-e8fc16e6bf33 - # https://github.com/benoitc/hackney/issues/273 @redirect_statuses [301, 302, 303, 307, 308] defp absolute_redirect_url(original_url, resp_headers) do location = diff --git a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex index b0d07a6f8..43c2c0449 100644 --- a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex @@ -27,7 +27,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do end defp fetch(url) do - http_client_opts = Pleroma.Config.get([:media_proxy, :proxy_opts, :http], pool: :media) + # This module uses Tesla (Pleroma.HTTP) to fetch the MediaProxy URL. + # Redirect following is handled by Tesla middleware, so we must not enable + # adapter-level redirect logic (Hackney can crash on relative redirects when proxied). + http_client_opts = + [:media_proxy, :proxy_opts, :http] + |> Pleroma.Config.get(pool: :media) + |> Keyword.drop([:follow_redirect, :force_redirect]) + HTTP.get(url, [], http_client_opts) end diff --git a/test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs b/test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs index 0da3afa3b..4b94b9ac7 100644 --- a/test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs @@ -54,14 +54,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicyTest do setup do: clear_config([:media_proxy, :enabled], true) test "it prefetches media proxy URIs" do - Tesla.Mock.mock(fn %{method: :get, url: "http://example.com/image.jpg"} -> - {:ok, %Tesla.Env{status: 200, body: ""}} - end) - - with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do + with_mock HTTP, + get: fn _, _, opts -> + send(self(), {:prefetch_opts, opts}) + {:ok, []} + end do MediaProxyWarmingPolicy.filter(@message) assert called(HTTP.get(:_, :_, :_)) + assert_receive {:prefetch_opts, opts} + refute Keyword.has_key?(opts, :follow_redirect) + refute Keyword.has_key?(opts, :force_redirect) end end @@ -81,10 +84,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicyTest do end test "history-aware" do - Tesla.Mock.mock(fn %{method: :get, url: "http://example.com/image.jpg"} -> - {:ok, %Tesla.Env{status: 200, body: ""}} - end) - with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do MRF.filter_one(MediaProxyWarmingPolicy, @message_with_history) @@ -93,10 +92,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicyTest do end test "works with Updates" do - Tesla.Mock.mock(fn %{method: :get, url: "http://example.com/image.jpg"} -> - {:ok, %Tesla.Env{status: 200, body: ""}} - end) - with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do MRF.filter_one(MediaProxyWarmingPolicy, @message_with_history |> Map.put("type", "Update")) From 54092d2b7c481aee51331feec029ab05d4303c60 Mon Sep 17 00:00:00 2001 From: Phantasm Date: Sun, 25 Jan 2026 06:46:28 +0100 Subject: [PATCH 40/40] Docs: Remove outdated, incorrect, inappropriate or unmaintained install docs --- README.md | 2 - changelog.d/inappropriate-docs.remove | 1 + docs/installation/arch_linux_en.md | 226 -------------------- docs/installation/netbsd_en.md | 294 -------------------------- docs/installation/nixos_en.md | 15 -- 5 files changed, 1 insertion(+), 537 deletions(-) create mode 100644 changelog.d/inappropriate-docs.remove delete mode 100644 docs/installation/arch_linux_en.md delete mode 100644 docs/installation/netbsd_en.md delete mode 100644 docs/installation/nixos_en.md diff --git a/README.md b/README.md index 2837b6ef8..8a5eb238f 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,6 @@ If you are running Linux (glibc or musl) on x86/arm, the recommended way to inst If your platform is not supported, or you just want to be able to edit the source code easily, you may install Pleroma from source. - [Alpine Linux](https://docs-develop.pleroma.social/backend/installation/alpine_linux_en/) -- [Arch Linux](https://docs-develop.pleroma.social/backend/installation/arch_linux_en/) -- [CentOS 7](https://docs-develop.pleroma.social/backend/installation/centos7_en/) - [Debian-based](https://docs-develop.pleroma.social/backend/installation/debian_based_en/) - [Debian-based (jp)](https://docs-develop.pleroma.social/backend/installation/debian_based_jp/) - [FreeBSD](https://docs-develop.pleroma.social/backend/installation/freebsd_en/) diff --git a/changelog.d/inappropriate-docs.remove b/changelog.d/inappropriate-docs.remove new file mode 100644 index 000000000..699c9186a --- /dev/null +++ b/changelog.d/inappropriate-docs.remove @@ -0,0 +1 @@ +Docs: Removed outdated, incorrect, unmaintained and inappropriate installation documentation (Arch, NetBSD, NixOS) diff --git a/docs/installation/arch_linux_en.md b/docs/installation/arch_linux_en.md deleted file mode 100644 index f7d722ef9..000000000 --- a/docs/installation/arch_linux_en.md +++ /dev/null @@ -1,226 +0,0 @@ -# Installing on Arch Linux - -{! backend/installation/otp_vs_from_source_source.include !} - -## Installation - -This guide will assume that you have administrative rights, either as root or a user with [sudo permissions](https://wiki.archlinux.org/index.php/Sudo). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su -s $SHELL -c 'command'` instead. - -### Required packages - -* `postgresql` -* `elixir` -* `git` -* `base-devel` -* `cmake` -* `file` - -#### Optional packages used in this guide - -* `nginx` (preferred, example configs for other reverse proxies can be found in the repo) -* `certbot` (or any other ACME client for Let’s Encrypt certificates) -* `ImageMagick` -* `ffmpeg` -* `exiftool` - -### Prepare the system - -* First update the system, if not already done: - -```shell -sudo pacman -Syu -``` - -* Install some of the above mentioned programs: - -```shell -sudo pacman -S git base-devel elixir cmake file -``` - -### Install PostgreSQL - -[Arch Wiki article](https://wiki.archlinux.org/index.php/PostgreSQL) - -* Install the `postgresql` package: - -```shell -sudo pacman -S postgresql -``` - -* Initialize the database cluster: - -```shell -sudo -iu postgres initdb -D /var/lib/postgres/data -``` - -* Start and enable the `postgresql.service` - -```shell -sudo systemctl enable --now postgresql.service -``` - -### Install media / graphics packages (optional, see [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md)) - -```shell -sudo pacman -S ffmpeg imagemagick perl-image-exiftool -``` - -### Install PleromaBE - -* Add a new system user for the Pleroma service: - -```shell -sudo useradd -r -s /bin/false -m -d /var/lib/pleroma -U pleroma -``` - -**Note**: To execute a single command as the Pleroma system user, use `sudo -Hu pleroma command`. You can also switch to a shell by using `sudo -Hu pleroma $SHELL`. If you don’t have and want `sudo` on your system, you can use `su` as root user (UID 0) for a single command by using `su -l pleroma -s $SHELL -c 'command'` and `su -l pleroma -s $SHELL` for starting a shell. - -* Git clone the PleromaBE repository and make the Pleroma user the owner of the directory: - -```shell -sudo mkdir -p /opt/pleroma -sudo chown -R pleroma:pleroma /opt/pleroma -sudo -Hu pleroma git clone -b stable https://git.pleroma.social/pleroma/pleroma /opt/pleroma -``` - -* Change to the new directory: - -```shell -cd /opt/pleroma -``` - -* Install the dependencies for Pleroma and answer with `yes` if it asks you to install `Hex`: - -```shell -sudo -Hu pleroma mix deps.get -``` - -* Generate the configuration: `sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen` - * Answer with `yes` if it asks you to install `rebar3`. - * This may take some time, because parts of pleroma get compiled first. - * After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`. - -* Check the configuration and if all looks right, rename it, so Pleroma will load it (`prod.secret.exs` for productive instance, `dev.secret.exs` for development instances): - -```shell -sudo -Hu pleroma mv config/{generated_config.exs,prod.secret.exs} -``` - -* The previous command creates also the file `config/setup_db.psql`, with which you can create the database: - -```shell -sudo -Hu postgres psql -f config/setup_db.psql -``` - -* Now run the database migration: - -```shell -sudo -Hu pleroma MIX_ENV=prod mix ecto.migrate -``` - -* Now you can start Pleroma already - -```shell -sudo -Hu pleroma MIX_ENV=prod mix phx.server -``` - -### Finalize installation - -If you want to open your newly installed instance to the world, you should run nginx or some other webserver/proxy in front of Pleroma and you should consider to create a systemd service file for Pleroma. - -#### Nginx - -* Install nginx, if not already done: - -```shell -sudo pacman -S nginx -``` - -* Create directories for available and enabled sites: - -```shell -sudo mkdir -p /etc/nginx/sites-{available,enabled} -``` - -* Append the following line at the end of the `http` block in `/etc/nginx/nginx.conf`: - -```Nginx -include sites-enabled/*; -``` - -* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it: - -```shell -sudo pacman -S certbot certbot-nginx -``` - -and then set it up: - -```shell -sudo mkdir -p /var/lib/letsencrypt/ -sudo certbot certonly --email -d --standalone -``` - -If that doesn’t work, make sure, that nginx is not already running. If it still doesn’t work, try setting up nginx first (change ssl “on” to “off” and try again). - ---- - -* Copy the example nginx configuration and activate it: - -```shell -sudo cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/sites-available/pleroma.nginx -sudo ln -s /etc/nginx/sites-available/pleroma.nginx /etc/nginx/sites-enabled/pleroma.nginx -``` - -* Before starting nginx edit the configuration and change it to your needs (e.g. change servername, change cert paths) - -* (Strongly recommended) serve media on another domain - -Refer to the [Hardening your instance](../configuration/hardening.md) document on how to serve media on another domain. We STRONGLY RECOMMEND you to do this to minimize attack vectors. - -* Enable and start nginx: - -```shell -sudo systemctl enable --now nginx.service -``` - -If you need to renew the certificate in the future, uncomment the relevant location block in the nginx config and run: - -```shell -sudo certbot certonly --email -d --webroot -w /var/lib/letsencrypt/ -``` - -#### Other webserver/proxies - -You can find example configurations for them in `/opt/pleroma/installation/`. - -#### Systemd service - -* Copy example service file - -```shell -sudo cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service -``` - -* Edit the service file and make sure that all paths fit your installation -* Enable and start `pleroma.service`: - -```shell -sudo systemctl enable --now pleroma.service -``` - -#### Create your first user - -If your instance is up and running, you can create your first user with administrative rights with the following task: - -```shell -sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new --admin -``` - -#### Further reading - -{! backend/installation/further_reading.include !} - -## Questions - -Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC. diff --git a/docs/installation/netbsd_en.md b/docs/installation/netbsd_en.md deleted file mode 100644 index 7c003700c..000000000 --- a/docs/installation/netbsd_en.md +++ /dev/null @@ -1,294 +0,0 @@ -# Installing on NetBSD - -{! backend/installation/generic_dependencies.include !} - -# Installation options - -Currently there are two options available for NetBSD: manual installation (from source) or using experimental package from [pkgsrc-wip](https://github.com/NetBSD/pkgsrc-wip/tree/master/pleroma). - -WIP package can be installed via pkgsrc and can be crosscompiled for easier binary distribution. Source installation most probably will be restricted to a single machine. - -## pkgsrc installation - -WIP package creates Mix.Release (similar to how Docker images are built) but doesn't bundle Erlang runtime, listing it as a dependency instead. This allows for easier and more modular installations, especially on weaker machines. Currently this method also does not support all features of `pleroma_ctl` command (like changing installation type or managing frontends) as NetBSD is not yet a supported binary flavour of Pleroma's CI. - -In any case, you can install it the same way as any other `pkgsrc-wip` package: - -``` -cd /usr/pkgsrc -git clone --depth 1 git://wip.pkgsrc.org/pkgsrc-wip.git wip -cp -rf wip/pleroma www -cp -rf wip/libvips graphics -cd /usr/pkgsrc/www/pleroma -bmake && bmake install -``` - -Use `bmake package` to create a binary package. This can come especially handy if you're targeting embedded or low-power systems and are crosscompiling on a more powerful machine. - -> Note: Elixir has [endianness bug](https://github.com/elixir-lang/elixir/issues/2785) which requires it to be compiled on a machine with the same endianness. In other words, package crosscompiled on amd64 (little endian) won't work on powerpc or sparc machines (big endian). While _in theory™_ nothing catastrophic should happen, one can see that for example regexes won't work properly. Some distributions just strip this warning away, so it doesn't bother the users... anyway, you've been warned. - -## Source installation - -pkgin should have been installed by the NetBSD installer if you selected -the right options. If it isn't installed, install it using `pkg_add`. - -Note that `postgresql11-contrib` is needed for the Postgres extensions -Pleroma uses. - -> Note: you can use modern versions of PostgreSQL. In this case, just use `postgresql16-contrib` and so on. - -The `mksh` shell is needed to run the Elixir `mix` script. - -`# pkgin install acmesh elixir git-base git-docs mksh nginx postgresql11-server postgresql11-client postgresql11-contrib sudo ffmpeg4 ImageMagick` - -You can also build these packages using pkgsrc: -``` -databases/postgresql11-contrib -databases/postgresql11-client -databases/postgresql11-server -devel/git-base -devel/git-docs -devel/cmake -lang/elixir -security/acmesh -security/sudo -shells/mksh -www/nginx -``` - -Create a user for Pleroma: - -``` -# groupadd pleroma -# useradd -d /home/pleroma -m -g pleroma -s /usr/pkg/bin/mksh pleroma -# echo 'export LC_ALL="en_GB.UTF-8"' >> /home/pleroma/.profile -# su -l pleroma -c $SHELL -``` - -Clone the repository: - -``` -$ cd /home/pleroma -$ git clone -b stable https://git.pleroma.social/pleroma/pleroma.git -``` - -Get deps and compile: - -``` -$ cd /home/pleroma/pleroma -$ export MIX_ENV=prod -$ mix deps.get -$ mix compile -``` - -## Install media / graphics packages (optional, see [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md)) - -`# pkgin install ImageMagick ffmpeg4 p5-Image-ExifTool` - -or via pkgsrc: - -``` -graphics/p5-Image-ExifTool -graphics/ImageMagick -multimedia/ffmpeg4 -``` - -# Configuration - -## Understanding $PREFIX - -From now on, you may encounter `$PREFIX` variable in the paths. This variable indicates your current local pkgsrc prefix. Usually it's `/usr/pkg` unless you configured it otherwise. Translating to pkgsrc's lingo, it's called `LOCALBASE`, which essentially means the same this. You may want to set it up for your local shell session (this uses `mksh` which should already be installed as one of the required dependencies): - -``` -$ export PREFIX=$(pkg_info -Q LOCALBASE mksh) -$ echo $PREFIX -/usr/pkg -``` - -## Setting up your instance - -Now, you need to configure your instance. During this initial configuration, you will be asked some questions about your server. You will need a domain name at this point; it doesn't have to be deployed, but changing it later will be very cumbersome. - -If you've installed via pkgsrc, `pleroma_ctl` should already be in your `PATH`; if you've installed from source, it's located at `/home/pleroma/pleroma/release/bin/pleroma_ctl`. - -``` -$ su -l pleroma -$ pleroma_ctl instance gen --output $PREFIX/etc/pleroma/config.exs --output-psql /tmp/setup_db.psql -``` - -During installation, you will be asked about static and upload directories. Don't forget to create them and update permissions: - -``` -mkdir -p /var/lib/pleroma/uploads -chown -R pleroma:pleroma /var/lib/pleroma -``` - -## Setting up the database - -First, run `# /etc/rc.d/pgsql start`. Then, `$ sudo -Hu pgsql -g pgsql createdb`. - -We can now initialize the database. You'll need to edit generated SQL file from the previous step. It's located at `/tmp/setup_db.psql`. - -Edit this file, and *change the password* to a password of your choice. Make sure it is secure, since -it'll be protecting your database. Now initialize the database: - -``` -$ sudo -Hu pgsql -g pgsql psql -f /tmp/setup_db.psql -``` - -Postgres allows connections from all users without a password by default. To -fix this, edit `$PREFIX/pgsql/data/pg_hba.conf`. Change every `trust` to -`password`. - -Once this is done, restart Postgres with `# /etc/rc.d/pgsql restart`. - -Run the database migrations. - -### pkgsrc installation - -``` -pleroma_ctl migrate -``` - -### Source installation - -You will need to do this whenever you update with `git pull`: - -``` -$ cd /home/pleroma/pleroma -$ MIX_ENV=prod mix ecto.migrate -``` - -## Configuring nginx - -Install the example configuration file -(`$PREFIX/share/examples/pleroma/pleroma.nginx` or `/home/pleroma/pleroma/installation/pleroma.nginx`) to -`$PREFIX/etc/nginx.conf`. - -Note that it will need to be wrapped in a `http {}` block. You should add -settings for the nginx daemon outside of the http block, for example: - -``` -user nginx nginx; -error_log /var/log/nginx/error.log; -worker_processes 4; - -events { -} -``` - -Edit the defaults: - -* Change `ssl_certificate` and `ssl_trusted_certificate` to -`/etc/nginx/tls/fullchain`. -* Change `ssl_certificate_key` to `/etc/nginx/tls/key`. -* Change `example.tld` to your instance's domain name. - -### (Strongly recommended) serve media on another domain - -Refer to the [Hardening your instance](../configuration/hardening.md) document on how to serve media on another domain. We STRONGLY RECOMMEND you to do this to minimize attack vectors. - -## Configuring acme.sh - -We'll be using acme.sh in Stateless Mode for TLS certificate renewal. - -First, get your account fingerprint: - -``` -$ sudo -Hu nginx -g nginx acme.sh --register-account -``` - -You need to add the following to your nginx configuration for the server -running on port 80: - -``` - location ~ ^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)$ { - default_type text/plain; - return 200 "$1.6fXAG9VyG0IahirPEU2ZerUtItW2DHzDzD9wZaEKpqd"; - } -``` - -Replace the string after after `$1.` with your fingerprint. - -Start nginx: - -``` -# /etc/rc.d/nginx start -``` - -It should now be possible to issue a cert (replace `example.com` -with your domain name): - -``` -$ sudo -Hu nginx -g nginx acme.sh --issue -d example.com --stateless -``` - -Let's add auto-renewal to `/etc/daily.local` -(replace `example.com` with your domain): - -``` -/usr/pkg/bin/sudo -Hu nginx -g nginx \ - /usr/pkg/sbin/acme.sh -r \ - -d example.com \ - --cert-file /etc/nginx/tls/cert \ - --key-file /etc/nginx/tls/key \ - --ca-file /etc/nginx/tls/ca \ - --fullchain-file /etc/nginx/tls/fullchain \ - --stateless -``` - -## Autostart - -For properly functioning instance, you will need pleroma (backend service), nginx (reverse proxy) and postgresql (database) services running. There's no requirement for them to reside on the same machine, but you have to provide autostart for each of them. - -### nginx -``` -# cp $PREFIX/share/examples/rc.d/nginx /etc/rc.d -# echo "nginx=YES" >> /etc/rc.conf -``` - -### postgresql - -``` -# cp $PREFIX/share/examples/rc.d/pgsql /etc/rc.d -# echo "pgsql=YES" >> /etc/rc.conf -``` - -### pleroma - -First, copy the script (pkgsrc variant) -``` -# cp $PREFIX/share/examples/pleroma/pleroma.rc /etc/rc.d/pleroma -``` - -or source variant -``` -# cp /home/pleroma/pleroma/installation/netbsd/rc.d/pleroma /etc/rc.d/pleroma -# chmod +x /etc/rc.d/pleroma -``` - -Then, add the following to `/etc/rc.conf`: - -``` -pleroma=YES -``` - -## Conclusion - -Run `# /etc/rc.d/pleroma start` to start Pleroma. -Restart nginx with `# /etc/rc.d/nginx restart` and you should be up and running. - -Make sure your time is in sync, or other instances will receive your posts with -incorrect timestamps. You should have ntpd running. - -## Instances running NetBSD - -* - -#### Further reading - -{! backend/installation/further_reading.include !} - -## Questions - -Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC. diff --git a/docs/installation/nixos_en.md b/docs/installation/nixos_en.md deleted file mode 100644 index f3c4988b1..000000000 --- a/docs/installation/nixos_en.md +++ /dev/null @@ -1,15 +0,0 @@ -# Installing on NixOS - -NixOS contains a source build package of pleroma and a NixOS module to install it. -For installation add this to your configuration.nix and add a config.exs next to it: -```nix - services.pleroma = { - enable = true; - configs = [ (lib.fileContents ./config.exs) ]; - secretConfigFile = "/var/lib/pleroma/secret.exs"; - }; -``` - -## Questions -The nix community uses matrix for communication: [#nix:nixos.org](https://matrix.to/#/#nix:nixos.org) -