Merge remote-tracking branch 'upstream/develop' into spc2
This commit is contained in:
commit
d499aae5df
|
@ -1,8 +1,8 @@
|
||||||
image: git.pleroma.social:5050/pleroma/pleroma/ci-base
|
image: git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.13.4-otp-24
|
||||||
|
|
||||||
variables: &global_variables
|
variables: &global_variables
|
||||||
# Only used for the release
|
# Only used for the release
|
||||||
ELIXIR_VER: 1.12.3
|
ELIXIR_VER: 1.13.4
|
||||||
POSTGRES_DB: pleroma_test
|
POSTGRES_DB: pleroma_test
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
|
@ -72,7 +72,7 @@ check-changelog:
|
||||||
tags:
|
tags:
|
||||||
- amd64
|
- amd64
|
||||||
|
|
||||||
build-1.12.3:
|
build-1.13.4:
|
||||||
extends:
|
extends:
|
||||||
- .build_changes_policy
|
- .build_changes_policy
|
||||||
- .using-ci-base
|
- .using-ci-base
|
||||||
|
@ -85,7 +85,7 @@ build-1.15.7-otp-25:
|
||||||
- .build_changes_policy
|
- .build_changes_policy
|
||||||
- .using-ci-base
|
- .using-ci-base
|
||||||
stage: build
|
stage: build
|
||||||
image: git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.15
|
image: git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.15-otp25
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
script:
|
script:
|
||||||
- mix compile --force
|
- mix compile --force
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
ARG ELIXIR_IMG=hexpm/elixir
|
ARG ELIXIR_IMG=hexpm/elixir
|
||||||
ARG ELIXIR_VER=1.12.3
|
ARG ELIXIR_VER=1.13.4
|
||||||
ARG ERLANG_VER=24.2.1
|
ARG ERLANG_VER=24.3.4.15
|
||||||
ARG ALPINE_VER=3.17.0
|
ARG ALPINE_VER=3.17.5
|
||||||
|
|
||||||
FROM ${ELIXIR_IMG}:${ELIXIR_VER}-erlang-${ERLANG_VER}-alpine-${ALPINE_VER} as build
|
FROM ${ELIXIR_IMG}:${ELIXIR_VER}-erlang-${ERLANG_VER}-alpine-${ALPINE_VER} as build
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
HTTP Security: By default, don't allow unsafe-eval. The setting needs to be changed to allow Flash emulation.
|
|
@ -0,0 +1 @@
|
||||||
|
Uploader: Add support for uploading attachments using IPFS
|
|
@ -0,0 +1 @@
|
||||||
|
Add NSFW-detecting MRF
|
|
@ -0,0 +1 @@
|
||||||
|
Add DNSRBL MRF
|
|
@ -0,0 +1 @@
|
||||||
|
Add options to the mix prune_objects task
|
|
@ -0,0 +1 @@
|
||||||
|
Add Anti-mention Spam MRF backported from Rebased
|
|
@ -0,0 +1 @@
|
||||||
|
HTTPSignaturePlug: Add :authorized_fetch_mode_exceptions configuration
|
|
@ -0,0 +1 @@
|
||||||
|
Add an option to reject certain domains when authorized fetch is enabled.
|
|
@ -0,0 +1 @@
|
||||||
|
Update Bandit to 1.5.2
|
|
@ -0,0 +1 @@
|
||||||
|
Elixir 1.13 is the minimum required version.
|
|
@ -0,0 +1 @@
|
||||||
|
Logger metadata is now attached to some logs to help with troubleshooting and analysis
|
|
@ -0,0 +1 @@
|
||||||
|
Ensure MediaProxy HTTP requests obey all the defined connection settings
|
|
@ -0,0 +1 @@
|
||||||
|
Add missing indexes on foreign key relationships
|
|
@ -0,0 +1 @@
|
||||||
|
Permit passing --chunk and --step values to the Pleroma.Search.Indexer Mix task
|
|
@ -0,0 +1 @@
|
||||||
|
noop
|
|
@ -0,0 +1 @@
|
||||||
|
Oban queues have refactored to simplify the queue design
|
|
@ -0,0 +1 @@
|
||||||
|
HTTP connection pool adjustments
|
|
@ -0,0 +1 @@
|
||||||
|
Update the documentation for configuring Prometheus metrics.
|
|
@ -0,0 +1 @@
|
||||||
|
PromEx documentation
|
|
@ -0,0 +1 @@
|
||||||
|
pleroma_ctl: Use realpath(1) instead of readlink(1)
|
|
@ -0,0 +1 @@
|
||||||
|
A 422 error is returned when attempting to reply to a deleted status
|
|
@ -0,0 +1 @@
|
||||||
|
Parsing of RichMedia TTLs for Amazon URLs when query parameters are nil
|
|
@ -0,0 +1 @@
|
||||||
|
Monitoring of search backend health to control the processing of jobs in the search indexing Oban queue
|
|
@ -0,0 +1 @@
|
||||||
|
Display reposted replies with exclude_replies: true
|
|
@ -0,0 +1 @@
|
||||||
|
Support honk-style attachment summaries as alt-text.
|
|
@ -0,0 +1 @@
|
||||||
|
Video thumbnails were not being generated due to a negative cache lookup logic error
|
|
@ -0,0 +1,8 @@
|
||||||
|
FROM elixir:1.13.4-otp-24
|
||||||
|
|
||||||
|
# Single RUN statement, otherwise intermediate images are created
|
||||||
|
# https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#run
|
||||||
|
RUN apt-get update &&\
|
||||||
|
apt-get install -y libmagic-dev cmake libimage-exiftool-perl ffmpeg &&\
|
||||||
|
mix local.hex --force &&\
|
||||||
|
mix local.rebar --force
|
|
@ -0,0 +1 @@
|
||||||
|
docker buildx build --platform linux/amd64,linux/arm64 -t git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.13.4-otp-24 --push .
|
|
@ -1 +1 @@
|
||||||
docker buildx build --platform linux/amd64 -t git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.15-otp25 --push .
|
docker buildx build --platform linux/amd64,linux/arm64 -t git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.15-otp25 --push .
|
||||||
|
|
|
@ -82,6 +82,10 @@
|
||||||
# region: "us-east-1", # may be required for Amazon AWS
|
# region: "us-east-1", # may be required for Amazon AWS
|
||||||
scheme: "https://"
|
scheme: "https://"
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.Uploaders.IPFS,
|
||||||
|
post_gateway_url: nil,
|
||||||
|
get_gateway_url: nil
|
||||||
|
|
||||||
config :pleroma, :emoji,
|
config :pleroma, :emoji,
|
||||||
shortcode_globs: ["/emoji/custom/**/*.png"],
|
shortcode_globs: ["/emoji/custom/**/*.png"],
|
||||||
pack_extensions: [".png", ".gif"],
|
pack_extensions: [".png", ".gif"],
|
||||||
|
@ -188,6 +192,7 @@
|
||||||
allow_relay: true,
|
allow_relay: true,
|
||||||
public: true,
|
public: true,
|
||||||
quarantined_instances: [],
|
quarantined_instances: [],
|
||||||
|
rejected_instances: [],
|
||||||
static_dir: "instance/static/",
|
static_dir: "instance/static/",
|
||||||
allowed_post_formats: [
|
allowed_post_formats: [
|
||||||
"text/plain",
|
"text/plain",
|
||||||
|
@ -406,15 +411,33 @@
|
||||||
accept: [],
|
accept: [],
|
||||||
reject: []
|
reject: []
|
||||||
|
|
||||||
|
config :pleroma, :mrf_dnsrbl,
|
||||||
|
nameserver: "127.0.0.1",
|
||||||
|
port: 53,
|
||||||
|
zone: "bl.pleroma.com"
|
||||||
|
|
||||||
# threshold of 7 days
|
# threshold of 7 days
|
||||||
config :pleroma, :mrf_object_age,
|
config :pleroma, :mrf_object_age,
|
||||||
threshold: 604_800,
|
threshold: 604_800,
|
||||||
actions: [:delist, :strip_followers]
|
actions: [:delist, :strip_followers]
|
||||||
|
|
||||||
|
config :pleroma, :mrf_nsfw_api,
|
||||||
|
url: "http://127.0.0.1:5000/",
|
||||||
|
threshold: 0.7,
|
||||||
|
mark_sensitive: true,
|
||||||
|
unlist: false,
|
||||||
|
reject: false
|
||||||
|
|
||||||
config :pleroma, :mrf_follow_bot, follower_nickname: nil
|
config :pleroma, :mrf_follow_bot, follower_nickname: nil
|
||||||
|
|
||||||
config :pleroma, :mrf_inline_quote, template: "<bdi>RT:</bdi> {url}"
|
config :pleroma, :mrf_inline_quote, template: "<bdi>RT:</bdi> {url}"
|
||||||
|
|
||||||
|
config :pleroma, :mrf_force_mention,
|
||||||
|
mention_parent: true,
|
||||||
|
mention_quoted: true
|
||||||
|
|
||||||
|
config :pleroma, :mrf_antimentionspam, user_age_limit: 30_000
|
||||||
|
|
||||||
config :pleroma, :rich_media,
|
config :pleroma, :rich_media,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
ignore_hosts: [],
|
ignore_hosts: [],
|
||||||
|
@ -497,7 +520,8 @@
|
||||||
sts: false,
|
sts: false,
|
||||||
sts_max_age: 31_536_000,
|
sts_max_age: 31_536_000,
|
||||||
ct_max_age: 2_592_000,
|
ct_max_age: 2_592_000,
|
||||||
referrer_policy: "same-origin"
|
referrer_policy: "same-origin",
|
||||||
|
allow_unsafe_eval: false
|
||||||
|
|
||||||
config :cors_plug,
|
config :cors_plug,
|
||||||
max_age: 86_400,
|
max_age: 86_400,
|
||||||
|
@ -559,24 +583,14 @@
|
||||||
log: false,
|
log: false,
|
||||||
queues: [
|
queues: [
|
||||||
activity_expiration: 10,
|
activity_expiration: 10,
|
||||||
token_expiration: 5,
|
|
||||||
filter_expiration: 1,
|
|
||||||
backup: 1,
|
|
||||||
federator_incoming: 5,
|
federator_incoming: 5,
|
||||||
federator_outgoing: 5,
|
federator_outgoing: 5,
|
||||||
ingestion_queue: 50,
|
ingestion_queue: 50,
|
||||||
web_push: 50,
|
web_push: 50,
|
||||||
mailer: 10,
|
|
||||||
transmogrifier: 20,
|
transmogrifier: 20,
|
||||||
scheduled_activities: 10,
|
|
||||||
poll_notifications: 10,
|
|
||||||
background: 5,
|
background: 5,
|
||||||
remote_fetcher: 2,
|
search_indexing: [limit: 10, paused: true],
|
||||||
attachments_cleanup: 1,
|
slow: 1
|
||||||
new_users_digest: 1,
|
|
||||||
mute_expire: 5,
|
|
||||||
search_indexing: 10,
|
|
||||||
rich_media_expiration: 2
|
|
||||||
],
|
],
|
||||||
plugins: [Oban.Plugins.Pruner],
|
plugins: [Oban.Plugins.Pruner],
|
||||||
crontab: [
|
crontab: [
|
||||||
|
@ -814,22 +828,27 @@
|
||||||
|
|
||||||
config :pleroma, :pools,
|
config :pleroma, :pools,
|
||||||
federation: [
|
federation: [
|
||||||
size: 50,
|
size: 75,
|
||||||
max_waiting: 10,
|
max_waiting: 20,
|
||||||
recv_timeout: 10_000
|
recv_timeout: 10_000
|
||||||
],
|
],
|
||||||
media: [
|
media: [
|
||||||
size: 50,
|
size: 75,
|
||||||
|
max_waiting: 20,
|
||||||
|
recv_timeout: 15_000
|
||||||
|
],
|
||||||
|
rich_media: [
|
||||||
|
size: 25,
|
||||||
max_waiting: 20,
|
max_waiting: 20,
|
||||||
recv_timeout: 15_000
|
recv_timeout: 15_000
|
||||||
],
|
],
|
||||||
upload: [
|
upload: [
|
||||||
size: 25,
|
size: 25,
|
||||||
max_waiting: 5,
|
max_waiting: 20,
|
||||||
recv_timeout: 15_000
|
recv_timeout: 15_000
|
||||||
],
|
],
|
||||||
default: [
|
default: [
|
||||||
size: 10,
|
size: 50,
|
||||||
max_waiting: 2,
|
max_waiting: 2,
|
||||||
recv_timeout: 5_000
|
recv_timeout: 5_000
|
||||||
]
|
]
|
||||||
|
@ -843,6 +862,10 @@
|
||||||
max_connections: 50,
|
max_connections: 50,
|
||||||
timeout: 150_000
|
timeout: 150_000
|
||||||
],
|
],
|
||||||
|
rich_media: [
|
||||||
|
max_connections: 50,
|
||||||
|
timeout: 150_000
|
||||||
|
],
|
||||||
upload: [
|
upload: [
|
||||||
max_connections: 25,
|
max_connections: 25,
|
||||||
timeout: 300_000
|
timeout: 300_000
|
||||||
|
@ -888,8 +911,6 @@
|
||||||
process_chunk_size: 100
|
process_chunk_size: 100
|
||||||
|
|
||||||
config :pleroma, ConcurrentLimiter, [
|
config :pleroma, ConcurrentLimiter, [
|
||||||
{Pleroma.Web.RichMedia.Helpers, [max_running: 5, max_waiting: 5]},
|
|
||||||
{Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy, [max_running: 5, max_waiting: 5]},
|
|
||||||
{Pleroma.Search, [max_running: 30, max_waiting: 50]}
|
{Pleroma.Search, [max_running: 30, max_waiting: 50]}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -915,6 +936,9 @@
|
||||||
qdrant_url: "http://127.0.0.1:6333/",
|
qdrant_url: "http://127.0.0.1:6333/",
|
||||||
qdrant_api_key: "",
|
qdrant_api_key: "",
|
||||||
openai_url: "http://127.0.0.1:11345",
|
openai_url: "http://127.0.0.1:11345",
|
||||||
|
# The healthcheck url has to be set to nil when used with the real openai
|
||||||
|
# API, as it doesn't have a healthcheck endpoint.
|
||||||
|
openai_healthcheck_url: "http://127.0.0.1:11345/health",
|
||||||
openai_model: "snowflake/snowflake-arctic-embed-xs",
|
openai_model: "snowflake/snowflake-arctic-embed-xs",
|
||||||
openai_api_key: "",
|
openai_api_key: "",
|
||||||
qdrant_index_configuration: %{
|
qdrant_index_configuration: %{
|
||||||
|
|
|
@ -136,6 +136,31 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
%{
|
||||||
|
group: :pleroma,
|
||||||
|
key: Pleroma.Uploaders.IPFS,
|
||||||
|
type: :group,
|
||||||
|
description: "IPFS uploader-related settings",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :get_gateway_url,
|
||||||
|
type: :string,
|
||||||
|
description: "GET Gateway URL",
|
||||||
|
suggestions: [
|
||||||
|
"https://ipfs.mydomain.com/{CID}",
|
||||||
|
"https://{CID}.ipfs.mydomain.com/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :post_gateway_url,
|
||||||
|
type: :string,
|
||||||
|
description: "POST Gateway URL",
|
||||||
|
suggestions: [
|
||||||
|
"http://localhost:5001/"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: Pleroma.Uploaders.S3,
|
key: Pleroma.Uploaders.S3,
|
||||||
|
@ -749,6 +774,18 @@
|
||||||
{"*.quarantined.com", "Reason"}
|
{"*.quarantined.com", "Reason"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
%{
|
||||||
|
key: :rejected_instances,
|
||||||
|
type: {:list, :tuple},
|
||||||
|
key_placeholder: "instance",
|
||||||
|
value_placeholder: "reason",
|
||||||
|
description:
|
||||||
|
"List of ActivityPub instances to reject requests from if authorized_fetch_mode is enabled",
|
||||||
|
suggestions: [
|
||||||
|
{"rejected.com", "Reason"},
|
||||||
|
{"*.rejected.com", "Reason"}
|
||||||
|
]
|
||||||
|
},
|
||||||
%{
|
%{
|
||||||
key: :static_dir,
|
key: :static_dir,
|
||||||
type: :string,
|
type: :string,
|
||||||
|
@ -1791,6 +1828,12 @@
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Require HTTP signatures for AP fetches"
|
description: "Require HTTP signatures for AP fetches"
|
||||||
},
|
},
|
||||||
|
%{
|
||||||
|
key: :authorized_fetch_mode_exceptions,
|
||||||
|
type: {:list, :string},
|
||||||
|
description:
|
||||||
|
"List of IPs (CIDR format accepted) to exempt from HTTP Signatures requirement (for example to allow debugging, you shouldn't otherwise need this)"
|
||||||
|
},
|
||||||
%{
|
%{
|
||||||
key: :note_replies_output_limit,
|
key: :note_replies_output_limit,
|
||||||
type: :integer,
|
type: :integer,
|
||||||
|
|
|
@ -153,6 +153,12 @@
|
||||||
config :pleroma, Pleroma.Upload, config_impl: Pleroma.UnstubbedConfigMock
|
config :pleroma, Pleroma.Upload, config_impl: Pleroma.UnstubbedConfigMock
|
||||||
config :pleroma, Pleroma.ScheduledActivity, config_impl: Pleroma.UnstubbedConfigMock
|
config :pleroma, Pleroma.ScheduledActivity, config_impl: Pleroma.UnstubbedConfigMock
|
||||||
config :pleroma, Pleroma.Web.RichMedia.Helpers, config_impl: Pleroma.StaticStubbedConfigMock
|
config :pleroma, Pleroma.Web.RichMedia.Helpers, config_impl: Pleroma.StaticStubbedConfigMock
|
||||||
|
config :pleroma, Pleroma.Uploaders.IPFS, config_impl: Pleroma.UnstubbedConfigMock
|
||||||
|
config :pleroma, Pleroma.Web.Plugs.HTTPSecurityPlug, config_impl: Pleroma.StaticStubbedConfigMock
|
||||||
|
config :pleroma, Pleroma.Web.Plugs.HTTPSignaturePlug, config_impl: Pleroma.StaticStubbedConfigMock
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.Web.Plugs.HTTPSignaturePlug,
|
||||||
|
http_signatures_impl: Pleroma.StubbedHTTPSignaturesMock
|
||||||
|
|
||||||
peer_module =
|
peer_module =
|
||||||
if String.to_integer(System.otp_release()) >= 25 do
|
if String.to_integer(System.otp_release()) >= 25 do
|
||||||
|
|
|
@ -21,16 +21,18 @@ Replaces embedded objects with references to them in the `objects` table. Only n
|
||||||
mix pleroma.database remove_embedded_objects [option ...]
|
mix pleroma.database remove_embedded_objects [option ...]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
- `--vacuum` - run `VACUUM FULL` after the embedded objects are replaced with their references
|
- `--vacuum` - run `VACUUM FULL` after the embedded objects are replaced with their references
|
||||||
|
|
||||||
## Prune old remote posts from the database
|
## Prune old remote posts from the database
|
||||||
|
|
||||||
This will prune remote posts older than 90 days (configurable with [`config :pleroma, :instance, remote_post_retention_days`](../../configuration/cheatsheet.md#instance)) from the database, they will be refetched from source when accessed.
|
This will prune remote posts older than 90 days (configurable with [`config :pleroma, :instance, remote_post_retention_days`](../../configuration/cheatsheet.md#instance)) from the database. Pruned posts may be refetched in some cases.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
The disk space will only be reclaimed after a proper vacuum. By default Postgresql does this for you on a regular basis, but if your instance has been running for a long time and there are many rows deleted, it may be advantageous to use `VACUUM FULL` (e.g. by using the `--vacuum` option).
|
||||||
|
|
||||||
!!! danger
|
!!! danger
|
||||||
The disk space will only be reclaimed after `VACUUM FULL`. You may run out of disk space during the execution of the task or vacuuming if you don't have about 1/3rds of the database size free.
|
You may run out of disk space during the execution of the task or vacuuming if you don't have about 1/3rds of the database size free. Vacuum causes a substantial increase in I/O traffic, and may lead to a degraded experience while it is running.
|
||||||
|
|
||||||
=== "OTP"
|
=== "OTP"
|
||||||
|
|
||||||
|
@ -45,7 +47,11 @@ This will prune remote posts older than 90 days (configurable with [`config :ple
|
||||||
```
|
```
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
- `--vacuum` - run `VACUUM FULL` after the objects are pruned
|
|
||||||
|
- `--keep-threads` - Don't prune posts when they are part of a thread where at least one post has seen local interaction (e.g. one of the posts is a local post, or is favourited by a local user, or has been repeated by a local user...). It also won't delete posts when at least one of the posts in that thread is kept (e.g. because one of the posts has seen recent activity).
|
||||||
|
- `--keep-non-public` - Keep non-public posts like DM's and followers-only, even if they are remote.
|
||||||
|
- `--prune-orphaned-activities` - Also prune orphaned activities afterwards. Activities are things like Like, Create, Announce, Flag (aka reports). They can significantly help reduce the database size. Note: this can take a very long time.
|
||||||
|
- `--vacuum` - Run `VACUUM FULL` after the objects are pruned. This should not be used on a regular basis, but is useful if your instance has been running for a long time before pruning.
|
||||||
|
|
||||||
## Create a conversation for all existing DMs
|
## Create a conversation for all existing DMs
|
||||||
|
|
||||||
|
@ -93,6 +99,9 @@ Can be safely re-run
|
||||||
|
|
||||||
## Vacuum the database
|
## Vacuum the database
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
By default Postgresql has an autovacuum deamon running. While the tasks described here can help in some cases, they shouldn't be needed on a regular basis. See [the Postgresql docs on vacuuming](https://www.postgresql.org/docs/current/sql-vacuum.html) for more information on this.
|
||||||
|
|
||||||
### Analyze
|
### Analyze
|
||||||
|
|
||||||
Running an `analyze` vacuum job can improve performance by updating statistics used by the query planner. **It is safe to cancel this.**
|
Running an `analyze` vacuum job can improve performance by updating statistics used by the query planner. **It is safe to cancel this.**
|
||||||
|
|
|
@ -41,6 +41,7 @@ To add configuration to your config file, you can copy it from the base config.
|
||||||
* `allow_relay`: Permits remote instances to subscribe to all public posts of your instance. This may increase the visibility of your instance.
|
* `allow_relay`: Permits remote instances to subscribe to all public posts of your instance. This may increase the visibility of your instance.
|
||||||
* `public`: Makes the client API in authenticated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network. Note that there is a dependent setting restricting or allowing unauthenticated access to specific resources, see `restrict_unauthenticated` for more details.
|
* `public`: Makes the client API in authenticated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network. Note that there is a dependent setting restricting or allowing unauthenticated access to specific resources, see `restrict_unauthenticated` for more details.
|
||||||
* `quarantined_instances`: ActivityPub instances where private (DMs, followers-only) activities will not be send.
|
* `quarantined_instances`: ActivityPub instances where private (DMs, followers-only) activities will not be send.
|
||||||
|
* `rejected_instances`: ActivityPub instances to reject requests from if authorized_fetch_mode is enabled.
|
||||||
* `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML).
|
* `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML).
|
||||||
* `extended_nickname_format`: Set to `true` to use extended local nicknames format (allows underscores/dashes). This will break federation with
|
* `extended_nickname_format`: Set to `true` to use extended local nicknames format (allows underscores/dashes). This will break federation with
|
||||||
older software for theses nicknames.
|
older software for theses nicknames.
|
||||||
|
@ -284,6 +285,7 @@ Notes:
|
||||||
* `deny_follow_blocked`: Whether to disallow following an account that has blocked the user in question
|
* `deny_follow_blocked`: Whether to disallow following an account that has blocked the user in question
|
||||||
* `sign_object_fetches`: Sign object fetches with HTTP signatures
|
* `sign_object_fetches`: Sign object fetches with HTTP signatures
|
||||||
* `authorized_fetch_mode`: Require HTTP signatures for AP fetches
|
* `authorized_fetch_mode`: Require HTTP signatures for AP fetches
|
||||||
|
* `authorized_fetch_mode_exceptions`: List of IPs (CIDR format accepted) to exempt from HTTP Signatures requirement (for example to allow debugging, you shouldn't otherwise need this)
|
||||||
|
|
||||||
## Pleroma.User
|
## Pleroma.User
|
||||||
|
|
||||||
|
@ -472,6 +474,7 @@ This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls start
|
||||||
* ``ct_max_age``: The maximum age for the `Expect-CT` header if sent.
|
* ``ct_max_age``: The maximum age for the `Expect-CT` header if sent.
|
||||||
* ``referrer_policy``: The referrer policy to use, either `"same-origin"` or `"no-referrer"`.
|
* ``referrer_policy``: The referrer policy to use, either `"same-origin"` or `"no-referrer"`.
|
||||||
* ``report_uri``: Adds the specified url to `report-uri` and `report-to` group in CSP header.
|
* ``report_uri``: Adds the specified url to `report-uri` and `report-to` group in CSP header.
|
||||||
|
* `allow_unsafe_eval`: Adds `wasm-unsafe-eval` to the CSP header. Needed for some non-essential frontend features like Flash emulation.
|
||||||
|
|
||||||
### Pleroma.Web.Plugs.RemoteIp
|
### Pleroma.Web.Plugs.RemoteIp
|
||||||
|
|
||||||
|
@ -661,6 +664,19 @@ config :ex_aws, :s3,
|
||||||
host: "s3.eu-central-1.amazonaws.com"
|
host: "s3.eu-central-1.amazonaws.com"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Pleroma.Uploaders.IPFS
|
||||||
|
|
||||||
|
* `post_gateway_url`: URL with port of POST Gateway (unauthenticated)
|
||||||
|
* `get_gateway_url`: URL of public GET Gateway
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
config :pleroma, Pleroma.Uploaders.IPFS,
|
||||||
|
post_gateway_url: "http://localhost:5001",
|
||||||
|
get_gateway_url: "http://{CID}.ipfs.mydomain.com"
|
||||||
|
```
|
||||||
|
|
||||||
### Upload filters
|
### Upload filters
|
||||||
|
|
||||||
#### Pleroma.Upload.Filter.AnonymizeFilename
|
#### Pleroma.Upload.Filter.AnonymizeFilename
|
||||||
|
|
|
@ -295,9 +295,7 @@ See [Admin-API](admin_api.md)
|
||||||
"id": "9umDrYheeY451cQnEe",
|
"id": "9umDrYheeY451cQnEe",
|
||||||
"name": "Read later",
|
"name": "Read later",
|
||||||
"emoji": "🕓",
|
"emoji": "🕓",
|
||||||
"source": {
|
"emoji_url": null
|
||||||
"emoji": "🕓"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,44 +1,47 @@
|
||||||
# Prometheus Metrics
|
# Prometheus / OpenTelemetry Metrics
|
||||||
|
|
||||||
Pleroma includes support for exporting metrics via the [prometheus_ex](https://github.com/deadtrickster/prometheus.ex) library.
|
Pleroma includes support for exporting metrics via the [prom_ex](https://github.com/akoutmos/prom_ex) library.
|
||||||
|
The metrics are exposed by a dedicated webserver/port to improve privacy and security.
|
||||||
|
|
||||||
Config example:
|
Config example:
|
||||||
|
|
||||||
```
|
```
|
||||||
config :prometheus, Pleroma.Web.Endpoint.MetricsExporter,
|
config :pleroma, Pleroma.PromEx,
|
||||||
enabled: true,
|
disabled: false,
|
||||||
auth: {:basic, "myusername", "mypassword"},
|
manual_metrics_start_delay: :no_delay,
|
||||||
ip_whitelist: ["127.0.0.1"],
|
drop_metrics_groups: [],
|
||||||
path: "/api/pleroma/app_metrics",
|
grafana: [
|
||||||
format: :text
|
host: System.get_env("GRAFANA_HOST", "http://localhost:3000"),
|
||||||
```
|
auth_token: System.get_env("GRAFANA_TOKEN"),
|
||||||
|
upload_dashboards_on_start: false,
|
||||||
* `enabled` (Pleroma extension) enables the endpoint
|
folder_name: "BEAM",
|
||||||
* `ip_whitelist` (Pleroma extension) could be used to restrict access only to specified IPs
|
annotate_app_lifecycle: true
|
||||||
* `auth` sets the authentication (`false` for no auth; configurable to HTTP Basic Auth, see [prometheus-plugs](https://github.com/deadtrickster/prometheus-plugs#exporting) documentation)
|
],
|
||||||
* `format` sets the output format (`:text` or `:protobuf`)
|
metrics_server: [
|
||||||
* `path` sets the path to app metrics page
|
port: 4021,
|
||||||
|
path: "/metrics",
|
||||||
|
protocol: :http,
|
||||||
## `/api/pleroma/app_metrics`
|
pool_size: 5,
|
||||||
|
cowboy_opts: [],
|
||||||
### Exports Prometheus application metrics
|
auth_strategy: :none
|
||||||
|
],
|
||||||
* Method: `GET`
|
datasource: "Prometheus"
|
||||||
* Authentication: not required by default (see configuration options above)
|
|
||||||
* Params: none
|
|
||||||
* Response: text
|
|
||||||
|
|
||||||
## Grafana
|
|
||||||
|
|
||||||
### Config example
|
|
||||||
|
|
||||||
The following is a config example to use with [Grafana](https://grafana.com)
|
|
||||||
|
|
||||||
```
|
```
|
||||||
- job_name: 'beam'
|
|
||||||
metrics_path: /api/pleroma/app_metrics
|
PromEx supports the ability to automatically publish dashboards to your Grafana server as well as register Annotations. If you do not wish to configure this capability you must generate the dashboard JSON files and import them directly. You can find the mix commands in the upstream [documentation](https://hexdocs.pm/prom_ex/Mix.Tasks.PromEx.Dashboard.Export.html). You can find the list of modules enabled in Pleroma for which you should generate dashboards for by examining the contents of the `lib/pleroma/prom_ex.ex` module.
|
||||||
scheme: https
|
|
||||||
|
## prometheus.yml
|
||||||
|
|
||||||
|
The following is a bare minimum config example to use with [Prometheus](https://prometheus.io) or Prometheus-compatible software like [VictoriaMetrics](https://victoriametrics.com).
|
||||||
|
|
||||||
|
```
|
||||||
|
global:
|
||||||
|
scrape_interval: 15s
|
||||||
|
|
||||||
|
scrape_configs:
|
||||||
|
- job_name: 'pleroma'
|
||||||
|
scheme: http
|
||||||
static_configs:
|
static_configs:
|
||||||
- targets: ['pleroma.soykaf.com']
|
- targets: ['pleroma.soykaf.com:4021']
|
||||||
```
|
```
|
||||||
|
|
|
@ -14,7 +14,7 @@ Note: This article is potentially outdated because at this time we may not have
|
||||||
|
|
||||||
- PostgreSQL 11.0以上 (Ubuntu16.04では9.5しか提供されていないので,[](https://www.postgresql.org/download/linux/ubuntu/)こちらから新しいバージョンを入手してください)
|
- PostgreSQL 11.0以上 (Ubuntu16.04では9.5しか提供されていないので,[](https://www.postgresql.org/download/linux/ubuntu/)こちらから新しいバージョンを入手してください)
|
||||||
- `postgresql-contrib` 11.0以上 (同上)
|
- `postgresql-contrib` 11.0以上 (同上)
|
||||||
- Elixir 1.8 以上 ([Debianのリポジトリからインストールしないこと!!! ここからインストールすること!](https://elixir-lang.org/install.html#unix-and-unix-like)。または [asdf](https://github.com/asdf-vm/asdf) をpleromaユーザーでインストールしてください)
|
- Elixir 1.13 以上 ([Debianのリポジトリからインストールしないこと!!! ここからインストールすること!](https://elixir-lang.org/install.html#unix-and-unix-like)。または [asdf](https://github.com/asdf-vm/asdf) をpleromaユーザーでインストールしてください)
|
||||||
- `erlang-dev`
|
- `erlang-dev`
|
||||||
- `erlang-nox`
|
- `erlang-nox`
|
||||||
- `git`
|
- `git`
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
## Required dependencies
|
## Required dependencies
|
||||||
|
|
||||||
* PostgreSQL >=11.0
|
* PostgreSQL >=11.0
|
||||||
* Elixir >=1.11.0 <1.15
|
* Elixir >=1.13.0 <1.15
|
||||||
* Erlang OTP >=22.2.0 (supported: <27)
|
* Erlang OTP >=22.2.0 (supported: <27)
|
||||||
* git
|
* git
|
||||||
* file / libmagic
|
* file / libmagic
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
[Unit]
|
||||||
|
Description=NSFW API
|
||||||
|
After=docker.service
|
||||||
|
Requires=docker.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
TimeoutStartSec=0
|
||||||
|
Restart=always
|
||||||
|
ExecStartPre=-/usr/bin/docker stop %n
|
||||||
|
ExecStartPre=-/usr/bin/docker rm %n
|
||||||
|
ExecStartPre=/usr/bin/docker pull eugencepoi/nsfw_api:latest
|
||||||
|
ExecStart=/usr/bin/docker run --rm -p 127.0.0.1:5000:5000/tcp --env PORT=5000 --name %n eugencepoi/nsfw_api:latest
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
|
@ -67,43 +67,168 @@ def run(["prune_objects" | args]) do
|
||||||
OptionParser.parse(
|
OptionParser.parse(
|
||||||
args,
|
args,
|
||||||
strict: [
|
strict: [
|
||||||
vacuum: :boolean
|
vacuum: :boolean,
|
||||||
|
keep_threads: :boolean,
|
||||||
|
keep_non_public: :boolean,
|
||||||
|
prune_orphaned_activities: :boolean
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
start_pleroma()
|
start_pleroma()
|
||||||
|
|
||||||
deadline = Pleroma.Config.get([:instance, :remote_post_retention_days])
|
deadline = Pleroma.Config.get([:instance, :remote_post_retention_days])
|
||||||
|
time_deadline = NaiveDateTime.utc_now() |> NaiveDateTime.add(-(deadline * 86_400))
|
||||||
|
|
||||||
Logger.info("Pruning objects older than #{deadline} days")
|
log_message = "Pruning objects older than #{deadline} days"
|
||||||
|
|
||||||
time_deadline =
|
log_message =
|
||||||
NaiveDateTime.utc_now()
|
if Keyword.get(options, :keep_non_public) do
|
||||||
|> NaiveDateTime.add(-(deadline * 86_400))
|
log_message <> ", keeping non public posts"
|
||||||
|
else
|
||||||
|
log_message
|
||||||
|
end
|
||||||
|
|
||||||
from(o in Object,
|
log_message =
|
||||||
where:
|
if Keyword.get(options, :keep_threads) do
|
||||||
|
log_message <> ", keeping threads intact"
|
||||||
|
else
|
||||||
|
log_message
|
||||||
|
end
|
||||||
|
|
||||||
|
log_message =
|
||||||
|
if Keyword.get(options, :prune_orphaned_activities) do
|
||||||
|
log_message <> ", pruning orphaned activities"
|
||||||
|
else
|
||||||
|
log_message
|
||||||
|
end
|
||||||
|
|
||||||
|
log_message =
|
||||||
|
if Keyword.get(options, :vacuum) do
|
||||||
|
log_message <>
|
||||||
|
", doing a full vacuum (you shouldn't do this as a recurring maintanance task)"
|
||||||
|
else
|
||||||
|
log_message
|
||||||
|
end
|
||||||
|
|
||||||
|
Logger.info(log_message)
|
||||||
|
|
||||||
|
if Keyword.get(options, :keep_threads) do
|
||||||
|
# We want to delete objects from threads where
|
||||||
|
# 1. the newest post is still old
|
||||||
|
# 2. none of the activities is local
|
||||||
|
# 3. none of the activities is bookmarked
|
||||||
|
# 4. optionally none of the posts is non-public
|
||||||
|
deletable_context =
|
||||||
|
if Keyword.get(options, :keep_non_public) do
|
||||||
|
Pleroma.Activity
|
||||||
|
|> join(:left, [a], b in Pleroma.Bookmark, on: a.id == b.activity_id)
|
||||||
|
|> group_by([a], fragment("? ->> 'context'::text", a.data))
|
||||||
|
|> having(
|
||||||
|
[a],
|
||||||
|
not fragment(
|
||||||
|
# Posts (checked on Create Activity) is non-public
|
||||||
|
"bool_or((not(?->'to' \\? ? OR ?->'cc' \\? ?)) and ? ->> 'type' = 'Create')",
|
||||||
|
a.data,
|
||||||
|
^Pleroma.Constants.as_public(),
|
||||||
|
a.data,
|
||||||
|
^Pleroma.Constants.as_public(),
|
||||||
|
a.data
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Pleroma.Activity
|
||||||
|
|> join(:left, [a], b in Pleroma.Bookmark, on: a.id == b.activity_id)
|
||||||
|
|> group_by([a], fragment("? ->> 'context'::text", a.data))
|
||||||
|
end
|
||||||
|
|> having([a], max(a.updated_at) < ^time_deadline)
|
||||||
|
|> having([a], not fragment("bool_or(?)", a.local))
|
||||||
|
|> having([_, b], fragment("max(?::text) is null", b.id))
|
||||||
|
|> select([a], fragment("? ->> 'context'::text", a.data))
|
||||||
|
|
||||||
|
Pleroma.Object
|
||||||
|
|> where([o], fragment("? ->> 'context'::text", o.data) in subquery(deletable_context))
|
||||||
|
else
|
||||||
|
if Keyword.get(options, :keep_non_public) do
|
||||||
|
Pleroma.Object
|
||||||
|
|> where(
|
||||||
|
[o],
|
||||||
fragment(
|
fragment(
|
||||||
"?->'to' \\? ? OR ?->'cc' \\? ?",
|
"?->'to' \\? ? OR ?->'cc' \\? ?",
|
||||||
o.data,
|
o.data,
|
||||||
^Pleroma.Constants.as_public(),
|
^Pleroma.Constants.as_public(),
|
||||||
o.data,
|
o.data,
|
||||||
^Pleroma.Constants.as_public()
|
^Pleroma.Constants.as_public()
|
||||||
),
|
)
|
||||||
where: o.inserted_at < ^time_deadline,
|
)
|
||||||
where:
|
else
|
||||||
|
Pleroma.Object
|
||||||
|
end
|
||||||
|
|> where([o], o.updated_at < ^time_deadline)
|
||||||
|
|> where(
|
||||||
|
[o],
|
||||||
fragment("split_part(?->>'actor', '/', 3) != ?", o.data, ^Pleroma.Web.Endpoint.host())
|
fragment("split_part(?->>'actor', '/', 3) != ?", o.data, ^Pleroma.Web.Endpoint.host())
|
||||||
)
|
)
|
||||||
|
end
|
||||||
|> Repo.delete_all(timeout: :infinity)
|
|> Repo.delete_all(timeout: :infinity)
|
||||||
|
|
||||||
prune_hashtags_query = """
|
if !Keyword.get(options, :keep_threads) do
|
||||||
|
# Without the --keep-threads option, it's possible that bookmarked
|
||||||
|
# objects have been deleted. We remove the corresponding bookmarks.
|
||||||
|
"""
|
||||||
|
delete from public.bookmarks
|
||||||
|
where id in (
|
||||||
|
select b.id from public.bookmarks b
|
||||||
|
left join public.activities a on b.activity_id = a.id
|
||||||
|
left join public.objects o on a."data" ->> 'object' = o.data ->> 'id'
|
||||||
|
where o.id is null
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|> Repo.query([], timeout: :infinity)
|
||||||
|
end
|
||||||
|
|
||||||
|
if Keyword.get(options, :prune_orphaned_activities) do
|
||||||
|
# Prune activities who link to a single object
|
||||||
|
"""
|
||||||
|
delete from public.activities
|
||||||
|
where id in (
|
||||||
|
select a.id from public.activities a
|
||||||
|
left join public.objects o on a.data ->> 'object' = o.data ->> 'id'
|
||||||
|
left join public.activities a2 on a.data ->> 'object' = a2.data ->> 'id'
|
||||||
|
left join public.users u on a.data ->> 'object' = u.ap_id
|
||||||
|
where not a.local
|
||||||
|
and jsonb_typeof(a."data" -> 'object') = 'string'
|
||||||
|
and o.id is null
|
||||||
|
and a2.id is null
|
||||||
|
and u.id is null
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|> Repo.query([], timeout: :infinity)
|
||||||
|
|
||||||
|
# Prune activities who link to an array of objects
|
||||||
|
"""
|
||||||
|
delete from public.activities
|
||||||
|
where id in (
|
||||||
|
select a.id from public.activities a
|
||||||
|
join json_array_elements_text((a."data" -> 'object')::json) as j on jsonb_typeof(a."data" -> 'object') = 'array'
|
||||||
|
left join public.objects o on j.value = o.data ->> 'id'
|
||||||
|
left join public.activities a2 on j.value = a2.data ->> 'id'
|
||||||
|
left join public.users u on j.value = u.ap_id
|
||||||
|
group by a.id
|
||||||
|
having max(o.data ->> 'id') is null
|
||||||
|
and max(a2.data ->> 'id') is null
|
||||||
|
and max(u.ap_id) is null
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|> Repo.query([], timeout: :infinity)
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
DELETE FROM hashtags AS ht
|
DELETE FROM hashtags AS ht
|
||||||
WHERE NOT EXISTS (
|
WHERE NOT EXISTS (
|
||||||
SELECT 1 FROM hashtags_objects hto
|
SELECT 1 FROM hashtags_objects hto
|
||||||
WHERE ht.id = hto.hashtag_id)
|
WHERE ht.id = hto.hashtag_id)
|
||||||
"""
|
"""
|
||||||
|
|> Repo.query()
|
||||||
Repo.query(prune_hashtags_query)
|
|
||||||
|
|
||||||
if Keyword.get(options, :vacuum) do
|
if Keyword.get(options, :vacuum) do
|
||||||
Maintenance.vacuum("full")
|
Maintenance.vacuum("full")
|
||||||
|
|
|
@ -33,15 +33,18 @@ def run(["index" | options]) do
|
||||||
OptionParser.parse(
|
OptionParser.parse(
|
||||||
options,
|
options,
|
||||||
strict: [
|
strict: [
|
||||||
limit: :integer
|
chunk: :integer,
|
||||||
|
limit: :integer,
|
||||||
|
step: :integer
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
start_pleroma()
|
start_pleroma()
|
||||||
|
|
||||||
|
chunk_size = Keyword.get(options, :chunk, 100)
|
||||||
limit = Keyword.get(options, :limit, 100_000)
|
limit = Keyword.get(options, :limit, 100_000)
|
||||||
|
per_step = Keyword.get(options, :step, 1000)
|
||||||
|
|
||||||
per_step = 1000
|
|
||||||
chunks = max(div(limit, per_step), 1)
|
chunks = max(div(limit, per_step), 1)
|
||||||
|
|
||||||
1..chunks
|
1..chunks
|
||||||
|
@ -65,7 +68,7 @@ def run(["index" | options]) do
|
||||||
IO.puts("Got #{length(ids)} activities, adding to indexer")
|
IO.puts("Got #{length(ids)} activities, adding to indexer")
|
||||||
|
|
||||||
ids
|
ids
|
||||||
|> Enum.chunk_every(100)
|
|> Enum.chunk_every(chunk_size)
|
||||||
|> Enum.each(fn chunk ->
|
|> Enum.each(fn chunk ->
|
||||||
IO.puts("Adding #{length(chunk)} activities to indexing queue")
|
IO.puts("Adding #{length(chunk)} activities to indexing queue")
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ defmodule Pleroma.Application do
|
||||||
@name Mix.Project.config()[:name]
|
@name Mix.Project.config()[:name]
|
||||||
@version Mix.Project.config()[:version]
|
@version Mix.Project.config()[:version]
|
||||||
@repository Mix.Project.config()[:source_url]
|
@repository Mix.Project.config()[:source_url]
|
||||||
|
@compile_env Mix.env()
|
||||||
|
|
||||||
def name, do: @name
|
def name, do: @name
|
||||||
def version, do: @version
|
def version, do: @version
|
||||||
|
@ -51,7 +52,11 @@ def start(_type, _args) do
|
||||||
Pleroma.HTML.compile_scrubbers()
|
Pleroma.HTML.compile_scrubbers()
|
||||||
Pleroma.Config.Oban.warn()
|
Pleroma.Config.Oban.warn()
|
||||||
Config.DeprecationWarnings.warn()
|
Config.DeprecationWarnings.warn()
|
||||||
|
|
||||||
|
if @compile_env != :test do
|
||||||
Pleroma.Web.Plugs.HTTPSecurityPlug.warn_if_disabled()
|
Pleroma.Web.Plugs.HTTPSecurityPlug.warn_if_disabled()
|
||||||
|
end
|
||||||
|
|
||||||
Pleroma.ApplicationRequirements.verify!()
|
Pleroma.ApplicationRequirements.verify!()
|
||||||
load_custom_modules()
|
load_custom_modules()
|
||||||
Pleroma.Docs.JSON.compile()
|
Pleroma.Docs.JSON.compile()
|
||||||
|
@ -109,7 +114,8 @@ def start(_type, _args) do
|
||||||
streamer_registry() ++
|
streamer_registry() ++
|
||||||
background_migrators() ++
|
background_migrators() ++
|
||||||
shout_child(shout_enabled?()) ++
|
shout_child(shout_enabled?()) ++
|
||||||
[Pleroma.Gopher.Server]
|
[Pleroma.Gopher.Server] ++
|
||||||
|
[Pleroma.Search.Healthcheck]
|
||||||
|
|
||||||
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
|
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
|
||||||
# for other strategies and supported options
|
# for other strategies and supported options
|
||||||
|
|
|
@ -16,4 +16,15 @@ def parse_address(ip) when is_binary(ip) do
|
||||||
def parse_address(ip) do
|
def parse_address(ip) do
|
||||||
:inet.parse_address(ip)
|
:inet.parse_address(ip)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def parse_cidr(proxy) when is_binary(proxy) do
|
||||||
|
proxy =
|
||||||
|
cond do
|
||||||
|
"/" in String.codepoints(proxy) -> proxy
|
||||||
|
InetCidr.v4?(InetCidr.parse_address!(proxy)) -> proxy <> "/32"
|
||||||
|
InetCidr.v6?(InetCidr.parse_address!(proxy)) -> proxy <> "/128"
|
||||||
|
end
|
||||||
|
|
||||||
|
InetCidr.parse_cidr!(proxy, true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,7 +25,7 @@ def missing_dependencies do
|
||||||
end
|
end
|
||||||
|
|
||||||
def image_resize(url, options) do
|
def image_resize(url, options) do
|
||||||
with {:ok, env} <- HTTP.get(url, [], pool: :media),
|
with {:ok, env} <- HTTP.get(url, [], http_client_opts()),
|
||||||
{:ok, resized} <-
|
{:ok, resized} <-
|
||||||
Operation.thumbnail_buffer(env.body, options.max_width,
|
Operation.thumbnail_buffer(env.body, options.max_width,
|
||||||
height: options.max_height,
|
height: options.max_height,
|
||||||
|
@ -45,8 +45,8 @@ def image_resize(url, options) do
|
||||||
@spec video_framegrab(String.t()) :: {:ok, binary()} | {:error, any()}
|
@spec video_framegrab(String.t()) :: {:ok, binary()} | {:error, any()}
|
||||||
def video_framegrab(url) do
|
def video_framegrab(url) do
|
||||||
with executable when is_binary(executable) <- System.find_executable("ffmpeg"),
|
with executable when is_binary(executable) <- System.find_executable("ffmpeg"),
|
||||||
false <- @cachex.exists?(:failed_media_helper_cache, url),
|
{:ok, false} <- @cachex.exists?(:failed_media_helper_cache, url),
|
||||||
{:ok, env} <- HTTP.get(url, [], pool: :media),
|
{:ok, env} <- HTTP.get(url, [], http_client_opts()),
|
||||||
{:ok, pid} <- StringIO.open(env.body) do
|
{:ok, pid} <- StringIO.open(env.body) do
|
||||||
body_stream = IO.binstream(pid, 1)
|
body_stream = IO.binstream(pid, 1)
|
||||||
|
|
||||||
|
@ -71,17 +71,19 @@ def video_framegrab(url) do
|
||||||
end)
|
end)
|
||||||
|
|
||||||
case Task.yield(task, 5_000) do
|
case Task.yield(task, 5_000) do
|
||||||
nil ->
|
{:ok, result} ->
|
||||||
|
{:ok, result}
|
||||||
|
|
||||||
|
_ ->
|
||||||
Task.shutdown(task)
|
Task.shutdown(task)
|
||||||
@cachex.put(:failed_media_helper_cache, url, nil)
|
@cachex.put(:failed_media_helper_cache, url, nil)
|
||||||
{:error, {:ffmpeg, :timeout}}
|
{:error, {:ffmpeg, :timeout}}
|
||||||
|
|
||||||
result ->
|
|
||||||
{:ok, result}
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
nil -> {:error, {:ffmpeg, :command_not_found}}
|
nil -> {:error, {:ffmpeg, :command_not_found}}
|
||||||
{:error, _} = error -> error
|
{:error, _} = error -> error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp http_client_opts, do: Pleroma.Config.get([:media_proxy, :proxy_opts, :http], pool: :media)
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
defmodule Pleroma.HTTPSignaturesAPI do
|
||||||
|
@callback validate_conn(conn :: Plug.Conn.t()) :: boolean
|
||||||
|
@callback signature_for_conn(conn :: Plug.Conn.t()) :: map
|
||||||
|
end
|
|
@ -489,7 +489,7 @@ def create_poll_notifications(%Activity{} = activity) do
|
||||||
|
|
||||||
NOTE: might be called for FAKE Activities, see ActivityPub.Utils.get_notified_from_object/1
|
NOTE: might be called for FAKE Activities, see ActivityPub.Utils.get_notified_from_object/1
|
||||||
"""
|
"""
|
||||||
@spec get_notified_from_activity(Activity.t(), boolean()) :: {list(User.t()), list(User.t())}
|
@spec get_notified_from_activity(Activity.t(), boolean()) :: list(User.t())
|
||||||
def get_notified_from_activity(activity, local_only \\ true)
|
def get_notified_from_activity(activity, local_only \\ true)
|
||||||
|
|
||||||
def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only)
|
def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only)
|
||||||
|
|
|
@ -204,7 +204,7 @@ def due_activities(offset \\ 0) do
|
||||||
|
|
||||||
def job_query(scheduled_activity_id) do
|
def job_query(scheduled_activity_id) do
|
||||||
from(j in Oban.Job,
|
from(j in Oban.Job,
|
||||||
where: j.queue == "scheduled_activities",
|
where: j.queue == "federator_outgoing",
|
||||||
where: fragment("args ->> 'activity_id' = ?::text", ^to_string(scheduled_activity_id))
|
where: fragment("args ->> 'activity_id' = ?::text", ^to_string(scheduled_activity_id))
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,8 +10,12 @@ def remove_from_index(%Pleroma.Object{id: object_id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def search(query, options) do
|
def search(query, options) do
|
||||||
search_module = Pleroma.Config.get([Pleroma.Search, :module], Pleroma.Activity)
|
search_module = Pleroma.Config.get([Pleroma.Search, :module])
|
||||||
|
|
||||||
search_module.search(options[:for_user], query, options)
|
search_module.search(options[:for_user], query, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def healthcheck_endpoints do
|
||||||
|
search_module = Pleroma.Config.get([Pleroma.Search, :module])
|
||||||
|
search_module.healthcheck_endpoints
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -28,7 +28,7 @@ def search(user, search_query, options \\ []) do
|
||||||
|> Activity.with_preloaded_object()
|
|> Activity.with_preloaded_object()
|
||||||
|> Activity.restrict_deactivated_users()
|
|> Activity.restrict_deactivated_users()
|
||||||
|> restrict_public(user)
|
|> restrict_public(user)
|
||||||
|> query_with(index_type, search_query, :websearch)
|
|> query_with(index_type, search_query)
|
||||||
|> maybe_restrict_local(user)
|
|> maybe_restrict_local(user)
|
||||||
|> maybe_restrict_author(author)
|
|> maybe_restrict_author(author)
|
||||||
|> maybe_restrict_blocked(user)
|
|> maybe_restrict_blocked(user)
|
||||||
|
@ -54,6 +54,9 @@ def create_index, do: :ok
|
||||||
@impl true
|
@impl true
|
||||||
def drop_index, do: :ok
|
def drop_index, do: :ok
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def healthcheck_endpoints, do: nil
|
||||||
|
|
||||||
def maybe_restrict_author(query, %User{} = author) do
|
def maybe_restrict_author(query, %User{} = author) do
|
||||||
Activity.Queries.by_author(query, author)
|
Activity.Queries.by_author(query, author)
|
||||||
end
|
end
|
||||||
|
@ -85,25 +88,7 @@ defp restrict_public(q, _user) do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp query_with(q, :gin, search_query, :plain) do
|
defp query_with(q, :gin, search_query) do
|
||||||
%{rows: [[tsc]]} =
|
|
||||||
Ecto.Adapters.SQL.query!(
|
|
||||||
Pleroma.Repo,
|
|
||||||
"select current_setting('default_text_search_config')::regconfig::oid;"
|
|
||||||
)
|
|
||||||
|
|
||||||
from([a, o] in q,
|
|
||||||
where:
|
|
||||||
fragment(
|
|
||||||
"to_tsvector(?::oid::regconfig, ?->>'content') @@ plainto_tsquery(?)",
|
|
||||||
^tsc,
|
|
||||||
o.data,
|
|
||||||
^search_query
|
|
||||||
)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp query_with(q, :gin, search_query, :websearch) do
|
|
||||||
%{rows: [[tsc]]} =
|
%{rows: [[tsc]]} =
|
||||||
Ecto.Adapters.SQL.query!(
|
Ecto.Adapters.SQL.query!(
|
||||||
Pleroma.Repo,
|
Pleroma.Repo,
|
||||||
|
@ -121,19 +106,7 @@ defp query_with(q, :gin, search_query, :websearch) do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp query_with(q, :rum, search_query, :plain) do
|
defp query_with(q, :rum, search_query) do
|
||||||
from([a, o] in q,
|
|
||||||
where:
|
|
||||||
fragment(
|
|
||||||
"? @@ plainto_tsquery(?)",
|
|
||||||
o.fts_content,
|
|
||||||
^search_query
|
|
||||||
),
|
|
||||||
order_by: [fragment("? <=> now()::date", o.inserted_at)]
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp query_with(q, :rum, search_query, :websearch) do
|
|
||||||
from([a, o] in q,
|
from([a, o] in q,
|
||||||
where:
|
where:
|
||||||
fragment(
|
fragment(
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
defmodule Pleroma.Search.Healthcheck do
|
||||||
|
@doc """
|
||||||
|
Monitors health of search backend to control processing of events based on health and availability.
|
||||||
|
"""
|
||||||
|
use GenServer
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@queue :search_indexing
|
||||||
|
@tick :timer.seconds(5)
|
||||||
|
@timeout :timer.seconds(2)
|
||||||
|
|
||||||
|
def start_link(_) do
|
||||||
|
GenServer.start_link(__MODULE__, [], name: __MODULE__)
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def init(_) do
|
||||||
|
state = %{healthy: false}
|
||||||
|
{:ok, state, {:continue, :start}}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_continue(:start, state) do
|
||||||
|
tick()
|
||||||
|
{:noreply, state}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_info(:check, state) do
|
||||||
|
urls = Pleroma.Search.healthcheck_endpoints()
|
||||||
|
|
||||||
|
new_state =
|
||||||
|
if check(urls) do
|
||||||
|
Oban.resume_queue(queue: @queue)
|
||||||
|
Map.put(state, :healthy, true)
|
||||||
|
else
|
||||||
|
Oban.pause_queue(queue: @queue)
|
||||||
|
Map.put(state, :healthy, false)
|
||||||
|
end
|
||||||
|
|
||||||
|
maybe_log_state_change(state, new_state)
|
||||||
|
|
||||||
|
tick()
|
||||||
|
{:noreply, new_state}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_call(:state, _from, state) do
|
||||||
|
{:reply, state, state, :hibernate}
|
||||||
|
end
|
||||||
|
|
||||||
|
def state, do: GenServer.call(__MODULE__, :state)
|
||||||
|
|
||||||
|
def check([]), do: true
|
||||||
|
|
||||||
|
def check(urls) when is_list(urls) do
|
||||||
|
Enum.all?(
|
||||||
|
urls,
|
||||||
|
fn url ->
|
||||||
|
case Pleroma.HTTP.get(url, [], recv_timeout: @timeout) do
|
||||||
|
{:ok, %{status: 200}} -> true
|
||||||
|
_ -> false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def check(_), do: true
|
||||||
|
|
||||||
|
defp tick do
|
||||||
|
Process.send_after(self(), :check, @tick)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_log_state_change(%{healthy: true}, %{healthy: false}) do
|
||||||
|
Logger.error("Pausing Oban queue #{@queue} due to search backend healthcheck failure")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_log_state_change(%{healthy: false}, %{healthy: true}) do
|
||||||
|
Logger.info("Resuming Oban queue #{@queue} due to search backend healthcheck pass")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_log_state_change(_, _), do: :ok
|
||||||
|
end
|
|
@ -184,4 +184,15 @@ def add_to_index(activity) do
|
||||||
def remove_from_index(object) do
|
def remove_from_index(object) do
|
||||||
meili_delete("/indexes/objects/documents/#{object.id}")
|
meili_delete("/indexes/objects/documents/#{object.id}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def healthcheck_endpoints do
|
||||||
|
endpoint =
|
||||||
|
Config.get([Pleroma.Search.Meilisearch, :url])
|
||||||
|
|> URI.parse()
|
||||||
|
|> Map.put(:path, "/health")
|
||||||
|
|> URI.to_string()
|
||||||
|
|
||||||
|
[endpoint]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Search.QdrantSearch do
|
||||||
alias __MODULE__.QdrantClient
|
alias __MODULE__.QdrantClient
|
||||||
|
|
||||||
import Pleroma.Search.Meilisearch, only: [object_to_search_data: 1]
|
import Pleroma.Search.Meilisearch, only: [object_to_search_data: 1]
|
||||||
|
import Pleroma.Search.DatabaseSearch, only: [maybe_fetch: 3]
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def create_index do
|
def create_index do
|
||||||
|
@ -115,8 +116,8 @@ def remove_from_index(object) do
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def search(_user, query, options) do
|
def search(user, original_query, options) do
|
||||||
query = "Represent this sentence for searching relevant passages: #{query}"
|
query = "Represent this sentence for searching relevant passages: #{original_query}"
|
||||||
|
|
||||||
with {:ok, embedding} <- get_embedding(query),
|
with {:ok, embedding} <- get_embedding(query),
|
||||||
{:ok, %{body: %{"result" => result}}} <-
|
{:ok, %{body: %{"result" => result}}} <-
|
||||||
|
@ -134,11 +135,25 @@ def search(_user, query, options) do
|
||||||
|> Activity.restrict_deactivated_users()
|
|> Activity.restrict_deactivated_users()
|
||||||
|> Ecto.Query.order_by([a], fragment("array_position(?, ?)", ^ids, a.id))
|
|> Ecto.Query.order_by([a], fragment("array_position(?, ?)", ^ids, a.id))
|
||||||
|> Pleroma.Repo.all()
|
|> Pleroma.Repo.all()
|
||||||
|
|> maybe_fetch(user, original_query)
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def healthcheck_endpoints do
|
||||||
|
qdrant_health =
|
||||||
|
Config.get([Pleroma.Search.QdrantSearch, :qdrant_url])
|
||||||
|
|> URI.parse()
|
||||||
|
|> Map.put(:path, "/healthz")
|
||||||
|
|> URI.to_string()
|
||||||
|
|
||||||
|
openai_health = Config.get([Pleroma.Search.QdrantSearch, :openai_healthcheck_url])
|
||||||
|
|
||||||
|
[qdrant_health, openai_health] |> Enum.filter(& &1)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defmodule Pleroma.Search.QdrantSearch.OpenAIClient do
|
defmodule Pleroma.Search.QdrantSearch.OpenAIClient do
|
||||||
|
|
|
@ -31,4 +31,12 @@ defmodule Pleroma.Search.SearchBackend do
|
||||||
Drop the index
|
Drop the index
|
||||||
"""
|
"""
|
||||||
@callback drop_index() :: :ok | {:error, any()}
|
@callback drop_index() :: :ok | {:error, any()}
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Healthcheck endpoints of search backend infrastructure to monitor for controlling
|
||||||
|
processing of jobs in the Oban queue.
|
||||||
|
|
||||||
|
It is expected a 200 response is healthy and other responses are unhealthy.
|
||||||
|
"""
|
||||||
|
@callback healthcheck_endpoints :: list() | nil
|
||||||
end
|
end
|
||||||
|
|
|
@ -62,8 +62,7 @@ defp remove_suffix(uri, [test | rest]) do
|
||||||
defp remove_suffix(uri, []), do: uri
|
defp remove_suffix(uri, []), do: uri
|
||||||
|
|
||||||
def fetch_public_key(conn) do
|
def fetch_public_key(conn) do
|
||||||
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
with {:ok, actor_id} <- get_actor_id(conn),
|
||||||
{:ok, actor_id} <- key_id_to_actor_id(kid),
|
|
||||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||||
{:ok, public_key}
|
{:ok, public_key}
|
||||||
else
|
else
|
||||||
|
@ -73,8 +72,7 @@ def fetch_public_key(conn) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def refetch_public_key(conn) do
|
def refetch_public_key(conn) do
|
||||||
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
with {:ok, actor_id} <- get_actor_id(conn),
|
||||||
{:ok, actor_id} <- key_id_to_actor_id(kid),
|
|
||||||
{:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
|
{:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
|
||||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||||
{:ok, public_key}
|
{:ok, public_key}
|
||||||
|
@ -84,6 +82,16 @@ def refetch_public_key(conn) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_actor_id(conn) do
|
||||||
|
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
||||||
|
{:ok, actor_id} <- key_id_to_actor_id(kid) do
|
||||||
|
{:ok, actor_id}
|
||||||
|
else
|
||||||
|
e ->
|
||||||
|
{:error, e}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def sign(%User{keys: keys} = user, headers) do
|
def sign(%User{keys: keys} = user, headers) do
|
||||||
with {:ok, private_key, _} <- Keys.keys_from_pem(keys) do
|
with {:ok, private_key, _} <- Keys.keys_from_pem(keys) do
|
||||||
HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers)
|
HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers)
|
||||||
|
|
|
@ -239,9 +239,13 @@ defp url_from_spec(%__MODULE__{name: name}, base_url, {:file, path}) do
|
||||||
""
|
""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if String.contains?(base_url, Pleroma.Uploaders.IPFS.placeholder()) do
|
||||||
|
String.replace(base_url, Pleroma.Uploaders.IPFS.placeholder(), path)
|
||||||
|
else
|
||||||
[base_url, path]
|
[base_url, path]
|
||||||
|> Path.join()
|
|> Path.join()
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp url_from_spec(_upload, _base_url, {:url, url}), do: url
|
defp url_from_spec(_upload, _base_url, {:url, url}), do: url
|
||||||
|
|
||||||
|
@ -277,6 +281,9 @@ def base_url do
|
||||||
Path.join([upload_base_url, bucket_with_namespace])
|
Path.join([upload_base_url, bucket_with_namespace])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Pleroma.Uploaders.IPFS ->
|
||||||
|
@config_impl.get([Pleroma.Uploaders.IPFS, :get_gateway_url])
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
public_endpoint || upload_base_url || Pleroma.Web.Endpoint.url() <> "/media/"
|
public_endpoint || upload_base_url || Pleroma.Web.Endpoint.url() <> "/media/"
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,8 +9,6 @@ defmodule Pleroma.Upload.Filter.Exiftool.StripLocation do
|
||||||
"""
|
"""
|
||||||
@behaviour Pleroma.Upload.Filter
|
@behaviour Pleroma.Upload.Filter
|
||||||
|
|
||||||
@spec filter(Pleroma.Upload.t()) :: {:ok, any()} | {:error, String.t()}
|
|
||||||
|
|
||||||
# Formats not compatible with exiftool at this time
|
# Formats not compatible with exiftool at this time
|
||||||
def filter(%Pleroma.Upload{content_type: "image/heic"}), do: {:ok, :noop}
|
def filter(%Pleroma.Upload{content_type: "image/heic"}), do: {:ok, :noop}
|
||||||
def filter(%Pleroma.Upload{content_type: "image/webp"}), do: {:ok, :noop}
|
def filter(%Pleroma.Upload{content_type: "image/webp"}), do: {:ok, :noop}
|
||||||
|
|
|
@ -38,7 +38,6 @@ defmodule Pleroma.Upload.Filter.Mogrifun do
|
||||||
[{"fill", "yellow"}, {"tint", "40"}]
|
[{"fill", "yellow"}, {"tint", "40"}]
|
||||||
]
|
]
|
||||||
|
|
||||||
@spec filter(Pleroma.Upload.t()) :: {:ok, atom()} | {:error, String.t()}
|
|
||||||
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
|
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
|
||||||
try do
|
try do
|
||||||
Filter.Mogrify.do_filter(file, [Enum.random(@filters)])
|
Filter.Mogrify.do_filter(file, [Enum.random(@filters)])
|
||||||
|
|
|
@ -8,7 +8,6 @@ defmodule Pleroma.Upload.Filter.Mogrify do
|
||||||
@type conversion :: action :: String.t() | {action :: String.t(), opts :: String.t()}
|
@type conversion :: action :: String.t() | {action :: String.t(), opts :: String.t()}
|
||||||
@type conversions :: conversion() | [conversion()]
|
@type conversions :: conversion() | [conversion()]
|
||||||
|
|
||||||
@spec filter(Pleroma.Upload.t()) :: {:ok, :atom} | {:error, String.t()}
|
|
||||||
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
|
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
|
||||||
try do
|
try do
|
||||||
do_filter(file, Pleroma.Config.get!([__MODULE__, :args]))
|
do_filter(file, Pleroma.Config.get!([__MODULE__, :args]))
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Uploaders.IPFS do
|
||||||
|
@behaviour Pleroma.Uploaders.Uploader
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
alias Tesla.Multipart
|
||||||
|
|
||||||
|
@config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config)
|
||||||
|
|
||||||
|
defp get_final_url(method) do
|
||||||
|
config = @config_impl.get([__MODULE__])
|
||||||
|
post_base_url = Keyword.get(config, :post_gateway_url)
|
||||||
|
|
||||||
|
Path.join([post_base_url, method])
|
||||||
|
end
|
||||||
|
|
||||||
|
def put_file_endpoint do
|
||||||
|
get_final_url("/api/v0/add")
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_file_endpoint do
|
||||||
|
get_final_url("/api/v0/files/rm")
|
||||||
|
end
|
||||||
|
|
||||||
|
@placeholder "{CID}"
|
||||||
|
def placeholder, do: @placeholder
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def get_file(file) do
|
||||||
|
b_url = Pleroma.Upload.base_url()
|
||||||
|
|
||||||
|
if String.contains?(b_url, @placeholder) do
|
||||||
|
{:ok, {:url, String.replace(b_url, @placeholder, URI.decode(file))}}
|
||||||
|
else
|
||||||
|
{:error, "IPFS Get URL doesn't contain 'cid' placeholder"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def put_file(%Pleroma.Upload{} = upload) do
|
||||||
|
mp =
|
||||||
|
Multipart.new()
|
||||||
|
|> Multipart.add_content_type_param("charset=utf-8")
|
||||||
|
|> Multipart.add_file(upload.tempfile)
|
||||||
|
|
||||||
|
case Pleroma.HTTP.post(put_file_endpoint(), mp, [], params: ["cid-version": "1"]) do
|
||||||
|
{:ok, ret} ->
|
||||||
|
case Jason.decode(ret.body) do
|
||||||
|
{:ok, ret} ->
|
||||||
|
if Map.has_key?(ret, "Hash") do
|
||||||
|
{:ok, {:file, ret["Hash"]}}
|
||||||
|
else
|
||||||
|
{:error, "JSON doesn't contain Hash key"}
|
||||||
|
end
|
||||||
|
|
||||||
|
error ->
|
||||||
|
Logger.error("#{__MODULE__}: #{inspect(error)}")
|
||||||
|
{:error, "JSON decode failed"}
|
||||||
|
end
|
||||||
|
|
||||||
|
error ->
|
||||||
|
Logger.error("#{__MODULE__}: #{inspect(error)}")
|
||||||
|
{:error, "IPFS Gateway upload failed"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def delete_file(file) do
|
||||||
|
case Pleroma.HTTP.post(delete_file_endpoint(), "", [], params: [arg: file]) do
|
||||||
|
{:ok, %{status: 204}} -> :ok
|
||||||
|
error -> {:error, inspect(error)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -2053,7 +2053,8 @@ defp verify_field_link(field, profile_urls) do
|
||||||
%{scheme: scheme, userinfo: nil, host: host}
|
%{scheme: scheme, userinfo: nil, host: host}
|
||||||
when not_empty_string(host) and scheme in ["http", "https"] <-
|
when not_empty_string(host) and scheme in ["http", "https"] <-
|
||||||
URI.parse(value),
|
URI.parse(value),
|
||||||
{:not_idn, true} <- {:not_idn, to_string(:idna.encode(host)) == host},
|
{:not_idn, true} <-
|
||||||
|
{:not_idn, match?(^host, to_string(:idna.encode(to_charlist(host))))},
|
||||||
"me" <- Pleroma.Web.RelMe.maybe_put_rel_me(value, profile_urls) do
|
"me" <- Pleroma.Web.RelMe.maybe_put_rel_me(value, profile_urls) do
|
||||||
CommonUtils.to_masto_date(NaiveDateTime.utc_now())
|
CommonUtils.to_masto_date(NaiveDateTime.utc_now())
|
||||||
else
|
else
|
||||||
|
@ -2727,7 +2728,7 @@ defp add_to_block(%User{} = user, %User{} = blocked) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec add_to_block(User.t(), User.t()) ::
|
@spec remove_from_block(User.t(), User.t()) ::
|
||||||
{:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
|
{:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
|
||||||
defp remove_from_block(%User{} = user, %User{} = blocked) do
|
defp remove_from_block(%User{} = user, %User{} = blocked) do
|
||||||
with {:ok, relationship} <- UserRelationship.delete_block(user, blocked) do
|
with {:ok, relationship} <- UserRelationship.delete_block(user, blocked) do
|
||||||
|
|
|
@ -979,8 +979,9 @@ defp restrict_media(query, _), do: query
|
||||||
|
|
||||||
defp restrict_replies(query, %{exclude_replies: true}) do
|
defp restrict_replies(query, %{exclude_replies: true}) do
|
||||||
from(
|
from(
|
||||||
[_activity, object] in query,
|
[activity, object] in query,
|
||||||
where: fragment("?->>'inReplyTo' is null", object.data)
|
where:
|
||||||
|
fragment("?->>'inReplyTo' is null or ?->>'type' = 'Announce'", object.data, activity.data)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -522,7 +522,7 @@ defp set_requester_reachable(%Plug.Conn{} = conn, _) do
|
||||||
conn
|
conn
|
||||||
end
|
end
|
||||||
|
|
||||||
defp log_inbox_metadata(conn = %{params: %{"actor" => actor, "type" => type}}, _) do
|
defp log_inbox_metadata(%{params: %{"actor" => actor, "type" => type}} = conn, _) do
|
||||||
Logger.metadata(actor: actor, type: type)
|
Logger.metadata(actor: actor, type: type)
|
||||||
conn
|
conn
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.AntiMentionSpamPolicy do
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.User
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
|
||||||
|
|
||||||
|
defp user_has_posted?(%User{} = u), do: u.note_count > 0
|
||||||
|
|
||||||
|
defp user_has_age?(%User{} = u) do
|
||||||
|
user_age_limit = Config.get([:mrf_antimentionspam, :user_age_limit], 30_000)
|
||||||
|
diff = NaiveDateTime.utc_now() |> NaiveDateTime.diff(u.inserted_at, :millisecond)
|
||||||
|
diff >= user_age_limit
|
||||||
|
end
|
||||||
|
|
||||||
|
defp good_reputation?(%User{} = u) do
|
||||||
|
user_has_age?(u) and user_has_posted?(u)
|
||||||
|
end
|
||||||
|
|
||||||
|
# copied from HellthreadPolicy
|
||||||
|
defp get_recipient_count(message) do
|
||||||
|
recipients = (message["to"] || []) ++ (message["cc"] || [])
|
||||||
|
|
||||||
|
follower_collection =
|
||||||
|
User.get_cached_by_ap_id(message["actor"] || message["attributedTo"]).follower_address
|
||||||
|
|
||||||
|
if Enum.member?(recipients, Pleroma.Constants.as_public()) do
|
||||||
|
recipients =
|
||||||
|
recipients
|
||||||
|
|> List.delete(Pleroma.Constants.as_public())
|
||||||
|
|> List.delete(follower_collection)
|
||||||
|
|
||||||
|
{:public, length(recipients)}
|
||||||
|
else
|
||||||
|
recipients =
|
||||||
|
recipients
|
||||||
|
|> List.delete(follower_collection)
|
||||||
|
|
||||||
|
{:not_public, length(recipients)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp object_has_recipients?(%{"object" => object} = activity) do
|
||||||
|
{_, object_count} = get_recipient_count(object)
|
||||||
|
{_, activity_count} = get_recipient_count(activity)
|
||||||
|
object_count + activity_count > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
defp object_has_recipients?(object) do
|
||||||
|
{_, count} = get_recipient_count(object)
|
||||||
|
count > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(%{"type" => "Create", "actor" => actor} = activity) do
|
||||||
|
with {:ok, %User{local: false} = u} <- User.get_or_fetch_by_ap_id(actor),
|
||||||
|
{:has_mentions, true} <- {:has_mentions, object_has_recipients?(activity)},
|
||||||
|
{:good_reputation, true} <- {:good_reputation, good_reputation?(u)} do
|
||||||
|
{:ok, activity}
|
||||||
|
else
|
||||||
|
{:ok, %User{local: true}} ->
|
||||||
|
{:ok, activity}
|
||||||
|
|
||||||
|
{:has_mentions, false} ->
|
||||||
|
{:ok, activity}
|
||||||
|
|
||||||
|
{:good_reputation, false} ->
|
||||||
|
{:reject, "[AntiMentionSpamPolicy] User rejected"}
|
||||||
|
|
||||||
|
{:error, _} ->
|
||||||
|
{:reject, "[AntiMentionSpamPolicy] Failed to get or fetch user by ap_id"}
|
||||||
|
|
||||||
|
e ->
|
||||||
|
{:reject, "[AntiMentionSpamPolicy] Unhandled error #{inspect(e)}"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# in all other cases, pass through
|
||||||
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
|
end
|
|
@ -0,0 +1,146 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.DNSRBLPolicy do
|
||||||
|
@moduledoc """
|
||||||
|
Dynamic activity filtering based on an RBL database
|
||||||
|
|
||||||
|
This MRF makes queries to a custom DNS server which will
|
||||||
|
respond with values indicating the classification of the domain
|
||||||
|
the activity originated from. This method has been widely used
|
||||||
|
in the email anti-spam industry for very fast reputation checks.
|
||||||
|
|
||||||
|
e.g., if the DNS response is 127.0.0.1 or empty, the domain is OK
|
||||||
|
Other values such as 127.0.0.2 may be used for specific classifications.
|
||||||
|
|
||||||
|
Information for why the host is blocked can be stored in a corresponding TXT record.
|
||||||
|
|
||||||
|
This method is fail-open so if the queries fail the activites are accepted.
|
||||||
|
|
||||||
|
An example of software meant for this purpsoe is rbldnsd which can be found
|
||||||
|
at http://www.corpit.ru/mjt/rbldnsd.html or mirrored at
|
||||||
|
https://git.pleroma.social/feld/rbldnsd
|
||||||
|
|
||||||
|
It is highly recommended that you run your own copy of rbldnsd and use an
|
||||||
|
external mechanism to sync/share the contents of the zone file. This is
|
||||||
|
important to keep the latency on the queries as low as possible and prevent
|
||||||
|
your DNS server from being attacked so it fails and content is permitted.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@query_retries 1
|
||||||
|
@query_timeout 500
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(%{"actor" => actor} = object) do
|
||||||
|
actor_info = URI.parse(actor)
|
||||||
|
|
||||||
|
with {:ok, object} <- check_rbl(actor_info, object) do
|
||||||
|
{:ok, object}
|
||||||
|
else
|
||||||
|
_ -> {:reject, "[DNSRBLPolicy]"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe do
|
||||||
|
mrf_dnsrbl =
|
||||||
|
Config.get(:mrf_dnsrbl)
|
||||||
|
|> Enum.into(%{})
|
||||||
|
|
||||||
|
{:ok, %{mrf_dnsrbl: mrf_dnsrbl}}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def config_description do
|
||||||
|
%{
|
||||||
|
key: :mrf_dnsrbl,
|
||||||
|
related_policy: "Pleroma.Web.ActivityPub.MRF.DNSRBLPolicy",
|
||||||
|
label: "MRF DNSRBL",
|
||||||
|
description: "DNS RealTime Blackhole Policy",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :nameserver,
|
||||||
|
type: {:string},
|
||||||
|
description: "DNSRBL Nameserver to Query (IP or hostame)",
|
||||||
|
suggestions: ["127.0.0.1"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :port,
|
||||||
|
type: {:string},
|
||||||
|
description: "Nameserver port",
|
||||||
|
suggestions: ["53"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :zone,
|
||||||
|
type: {:string},
|
||||||
|
description: "Root zone for querying",
|
||||||
|
suggestions: ["bl.pleroma.com"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp check_rbl(%{host: actor_host}, object) do
|
||||||
|
with false <- match?(^actor_host, Pleroma.Web.Endpoint.host()),
|
||||||
|
zone when not is_nil(zone) <- Keyword.get(Config.get([:mrf_dnsrbl]), :zone) do
|
||||||
|
query =
|
||||||
|
Enum.join([actor_host, zone], ".")
|
||||||
|
|> String.to_charlist()
|
||||||
|
|
||||||
|
rbl_response = rblquery(query)
|
||||||
|
|
||||||
|
if Enum.empty?(rbl_response) do
|
||||||
|
{:ok, object}
|
||||||
|
else
|
||||||
|
Task.start(fn ->
|
||||||
|
reason =
|
||||||
|
case rblquery(query, :txt) do
|
||||||
|
[[result]] -> result
|
||||||
|
_ -> "undefined"
|
||||||
|
end
|
||||||
|
|
||||||
|
Logger.warning(
|
||||||
|
"DNSRBL Rejected activity from #{actor_host} for reason: #{inspect(reason)}"
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
|
:error
|
||||||
|
end
|
||||||
|
else
|
||||||
|
_ -> {:ok, object}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_rblhost_ip(rblhost) do
|
||||||
|
case rblhost |> String.to_charlist() |> :inet_parse.address() do
|
||||||
|
{:ok, _} -> rblhost |> String.to_charlist() |> :inet_parse.address()
|
||||||
|
_ -> {:ok, rblhost |> String.to_charlist() |> :inet_res.lookup(:in, :a) |> Enum.random()}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp rblquery(query, type \\ :a) do
|
||||||
|
config = Config.get([:mrf_dnsrbl])
|
||||||
|
|
||||||
|
case get_rblhost_ip(config[:nameserver]) do
|
||||||
|
{:ok, rblnsip} ->
|
||||||
|
:inet_res.lookup(query, :in, type,
|
||||||
|
nameservers: [{rblnsip, config[:port]}],
|
||||||
|
timeout: @query_timeout,
|
||||||
|
retry: @query_retries
|
||||||
|
)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -11,11 +11,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@adapter_options [
|
|
||||||
pool: :media,
|
|
||||||
recv_timeout: 10_000
|
|
||||||
]
|
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def history_awareness, do: :auto
|
def history_awareness, do: :auto
|
||||||
|
|
||||||
|
@ -27,17 +22,14 @@ defp prefetch(url) do
|
||||||
|
|
||||||
Logger.debug("Prefetching #{inspect(url)} as #{inspect(prefetch_url)}")
|
Logger.debug("Prefetching #{inspect(url)} as #{inspect(prefetch_url)}")
|
||||||
|
|
||||||
if Pleroma.Config.get(:env) == :test do
|
|
||||||
fetch(prefetch_url)
|
fetch(prefetch_url)
|
||||||
else
|
|
||||||
ConcurrentLimiter.limit(__MODULE__, fn ->
|
|
||||||
Task.start(fn -> fetch(prefetch_url) end)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp fetch(url), do: HTTP.get(url, [], @adapter_options)
|
defp fetch(url) do
|
||||||
|
http_client_opts = Pleroma.Config.get([:media_proxy, :proxy_opts, :http], pool: :media)
|
||||||
|
HTTP.get(url, [], http_client_opts)
|
||||||
|
end
|
||||||
|
|
||||||
defp preload(%{"object" => %{"attachment" => attachments}} = _message) do
|
defp preload(%{"object" => %{"attachment" => attachments}} = _message) do
|
||||||
Enum.each(attachments, fn
|
Enum.each(attachments, fn
|
||||||
|
|
|
@ -0,0 +1,265 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do
|
||||||
|
@moduledoc """
|
||||||
|
Hide, delete, or mark sensitive NSFW content with artificial intelligence.
|
||||||
|
|
||||||
|
Requires a NSFW API server, configured like so:
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.Web.ActivityPub.MRF.NsfwMRF,
|
||||||
|
url: "http://127.0.0.1:5000/",
|
||||||
|
threshold: 0.7,
|
||||||
|
mark_sensitive: true,
|
||||||
|
unlist: false,
|
||||||
|
reject: false
|
||||||
|
|
||||||
|
The NSFW API server must implement an HTTP endpoint like this:
|
||||||
|
|
||||||
|
curl http://localhost:5000/?url=https://fedi.com/images/001.jpg
|
||||||
|
|
||||||
|
Returning a response like this:
|
||||||
|
|
||||||
|
{"score", 0.314}
|
||||||
|
|
||||||
|
Where a score is 0-1, with `1` being definitely NSFW.
|
||||||
|
|
||||||
|
A good API server is here: https://github.com/EugenCepoi/nsfw_api
|
||||||
|
You can run it with Docker with a one-liner:
|
||||||
|
|
||||||
|
docker run -it -p 127.0.0.1:5000:5000/tcp --env PORT=5000 eugencepoi/nsfw_api:latest
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
- `url`: Base URL of the API server. Default: "http://127.0.0.1:5000/"
|
||||||
|
- `threshold`: Lowest score to take action on. Default: `0.7`
|
||||||
|
- `mark_sensitive`: Mark sensitive all detected NSFW content? Default: `true`
|
||||||
|
- `unlist`: Unlist all detected NSFW content? Default: `false`
|
||||||
|
- `reject`: Reject all detected NSFW content (takes precedence)? Default: `false`
|
||||||
|
"""
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.Constants
|
||||||
|
alias Pleroma.HTTP
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
|
||||||
|
@policy :mrf_nsfw_api
|
||||||
|
|
||||||
|
def build_request_url(url) do
|
||||||
|
Config.get([@policy, :url])
|
||||||
|
|> URI.parse()
|
||||||
|
|> fix_path()
|
||||||
|
|> Map.put(:query, "url=#{url}")
|
||||||
|
|> URI.to_string()
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_url(url) do
|
||||||
|
request = build_request_url(url)
|
||||||
|
|
||||||
|
with {:ok, %Tesla.Env{body: body}} <- HTTP.get(request) do
|
||||||
|
Jason.decode(body)
|
||||||
|
else
|
||||||
|
error ->
|
||||||
|
Logger.warning("""
|
||||||
|
[NsfwApiPolicy]: The API server failed. Skipping.
|
||||||
|
#{inspect(error)}
|
||||||
|
""")
|
||||||
|
|
||||||
|
error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_url_nsfw(url) when is_binary(url) do
|
||||||
|
threshold = Config.get([@policy, :threshold])
|
||||||
|
|
||||||
|
case parse_url(url) do
|
||||||
|
{:ok, %{"score" => score}} when score >= threshold ->
|
||||||
|
{:nsfw, %{url: url, score: score, threshold: threshold}}
|
||||||
|
|
||||||
|
{:ok, %{"score" => score}} ->
|
||||||
|
{:sfw, %{url: url, score: score, threshold: threshold}}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:sfw, %{url: url, score: nil, threshold: threshold}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_url_nsfw(%{"href" => url}) when is_binary(url) do
|
||||||
|
check_url_nsfw(url)
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_url_nsfw(url) do
|
||||||
|
threshold = Config.get([@policy, :threshold])
|
||||||
|
{:sfw, %{url: url, score: nil, threshold: threshold}}
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_attachment_nsfw(%{"url" => urls} = attachment) when is_list(urls) do
|
||||||
|
if Enum.all?(urls, &match?({:sfw, _}, check_url_nsfw(&1))) do
|
||||||
|
{:sfw, attachment}
|
||||||
|
else
|
||||||
|
{:nsfw, attachment}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_attachment_nsfw(%{"url" => url} = attachment) when is_binary(url) do
|
||||||
|
case check_url_nsfw(url) do
|
||||||
|
{:sfw, _} -> {:sfw, attachment}
|
||||||
|
{:nsfw, _} -> {:nsfw, attachment}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_attachment_nsfw(attachment), do: {:sfw, attachment}
|
||||||
|
|
||||||
|
def check_object_nsfw(%{"attachment" => attachments} = object) when is_list(attachments) do
|
||||||
|
if Enum.all?(attachments, &match?({:sfw, _}, check_attachment_nsfw(&1))) do
|
||||||
|
{:sfw, object}
|
||||||
|
else
|
||||||
|
{:nsfw, object}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_object_nsfw(%{"object" => %{} = child_object} = object) do
|
||||||
|
case check_object_nsfw(child_object) do
|
||||||
|
{:sfw, _} -> {:sfw, object}
|
||||||
|
{:nsfw, _} -> {:nsfw, object}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_object_nsfw(object), do: {:sfw, object}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(object) do
|
||||||
|
with {:sfw, object} <- check_object_nsfw(object) do
|
||||||
|
{:ok, object}
|
||||||
|
else
|
||||||
|
{:nsfw, _data} -> handle_nsfw(object)
|
||||||
|
_ -> {:reject, "NSFW: Attachment rejected"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_nsfw(object) do
|
||||||
|
if Config.get([@policy, :reject]) do
|
||||||
|
{:reject, object}
|
||||||
|
else
|
||||||
|
{:ok,
|
||||||
|
object
|
||||||
|
|> maybe_unlist()
|
||||||
|
|> maybe_mark_sensitive()}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_unlist(object) do
|
||||||
|
if Config.get([@policy, :unlist]) do
|
||||||
|
unlist(object)
|
||||||
|
else
|
||||||
|
object
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_mark_sensitive(object) do
|
||||||
|
if Config.get([@policy, :mark_sensitive]) do
|
||||||
|
mark_sensitive(object)
|
||||||
|
else
|
||||||
|
object
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def unlist(%{"to" => to, "cc" => cc, "actor" => actor} = object) do
|
||||||
|
with %User{} = user <- User.get_cached_by_ap_id(actor) do
|
||||||
|
to =
|
||||||
|
[user.follower_address | to]
|
||||||
|
|> List.delete(Constants.as_public())
|
||||||
|
|> Enum.uniq()
|
||||||
|
|
||||||
|
cc =
|
||||||
|
[Constants.as_public() | cc]
|
||||||
|
|> List.delete(user.follower_address)
|
||||||
|
|> Enum.uniq()
|
||||||
|
|
||||||
|
object
|
||||||
|
|> Map.put("to", to)
|
||||||
|
|> Map.put("cc", cc)
|
||||||
|
else
|
||||||
|
_ -> raise "[NsfwApiPolicy]: Could not find user #{actor}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def mark_sensitive(%{"object" => child_object} = object) when is_map(child_object) do
|
||||||
|
Map.put(object, "object", mark_sensitive(child_object))
|
||||||
|
end
|
||||||
|
|
||||||
|
def mark_sensitive(object) when is_map(object) do
|
||||||
|
tags = (object["tag"] || []) ++ ["nsfw"]
|
||||||
|
|
||||||
|
object
|
||||||
|
|> Map.put("tag", tags)
|
||||||
|
|> Map.put("sensitive", true)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Hackney needs a trailing slash
|
||||||
|
defp fix_path(%URI{path: path} = uri) when is_binary(path) do
|
||||||
|
path = String.trim_trailing(path, "/") <> "/"
|
||||||
|
Map.put(uri, :path, path)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fix_path(%URI{path: nil} = uri), do: Map.put(uri, :path, "/")
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe do
|
||||||
|
options = %{
|
||||||
|
threshold: Config.get([@policy, :threshold]),
|
||||||
|
mark_sensitive: Config.get([@policy, :mark_sensitive]),
|
||||||
|
unlist: Config.get([@policy, :unlist]),
|
||||||
|
reject: Config.get([@policy, :reject])
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, %{@policy => options}}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def config_description do
|
||||||
|
%{
|
||||||
|
key: @policy,
|
||||||
|
related_policy: to_string(__MODULE__),
|
||||||
|
label: "NSFW API Policy",
|
||||||
|
description:
|
||||||
|
"Hide, delete, or mark sensitive NSFW content with artificial intelligence. Requires running an external API server.",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :url,
|
||||||
|
type: :string,
|
||||||
|
description: "Base URL of the API server.",
|
||||||
|
suggestions: ["http://127.0.0.1:5000/"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :threshold,
|
||||||
|
type: :float,
|
||||||
|
description: "Lowest score to take action on. Between 0 and 1.",
|
||||||
|
suggestions: [0.7]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :mark_sensitive,
|
||||||
|
type: :boolean,
|
||||||
|
description: "Mark sensitive all detected NSFW content?",
|
||||||
|
suggestions: [true]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :unlist,
|
||||||
|
type: :boolean,
|
||||||
|
description: "Unlist sensitive all detected NSFW content?",
|
||||||
|
suggestions: [false]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :reject,
|
||||||
|
type: :boolean,
|
||||||
|
description: "Reject sensitive all detected NSFW content (takes precedence)?",
|
||||||
|
suggestions: [false]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -15,6 +15,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
|
||||||
field(:type, :string, default: "Link")
|
field(:type, :string, default: "Link")
|
||||||
field(:mediaType, ObjectValidators.MIME, default: "application/octet-stream")
|
field(:mediaType, ObjectValidators.MIME, default: "application/octet-stream")
|
||||||
field(:name, :string)
|
field(:name, :string)
|
||||||
|
field(:summary, :string)
|
||||||
field(:blurhash, :string)
|
field(:blurhash, :string)
|
||||||
|
|
||||||
embeds_many :url, UrlObjectValidator, primary_key: false do
|
embeds_many :url, UrlObjectValidator, primary_key: false do
|
||||||
|
@ -44,7 +45,7 @@ def changeset(struct, data) do
|
||||||
|> fix_url()
|
|> fix_url()
|
||||||
|
|
||||||
struct
|
struct
|
||||||
|> cast(data, [:id, :type, :mediaType, :name, :blurhash])
|
|> cast(data, [:id, :type, :mediaType, :name, :summary, :blurhash])
|
||||||
|> cast_embed(:url, with: &url_changeset/2, required: true)
|
|> cast_embed(:url, with: &url_changeset/2, required: true)
|
||||||
|> validate_inclusion(:type, ~w[Link Document Audio Image Video])
|
|> validate_inclusion(:type, ~w[Link Document Audio Image Video])
|
||||||
|> validate_required([:type, :mediaType])
|
|> validate_required([:type, :mediaType])
|
||||||
|
|
|
@ -50,7 +50,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Attachment do
|
||||||
pleroma: %Schema{
|
pleroma: %Schema{
|
||||||
type: :object,
|
type: :object,
|
||||||
properties: %{
|
properties: %{
|
||||||
mime_type: %Schema{type: :string, description: "mime type of the attachment"}
|
mime_type: %Schema{type: :string, description: "mime type of the attachment"},
|
||||||
|
name: %Schema{
|
||||||
|
type: :string,
|
||||||
|
description: "Name of the attachment, typically the filename"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -129,8 +129,22 @@ defp attachments(%{params: params} = draft) do
|
||||||
|
|
||||||
defp in_reply_to(%{params: %{in_reply_to_status_id: ""}} = draft), do: draft
|
defp in_reply_to(%{params: %{in_reply_to_status_id: ""}} = draft), do: draft
|
||||||
|
|
||||||
defp in_reply_to(%{params: %{in_reply_to_status_id: id}} = draft) when is_binary(id) do
|
defp in_reply_to(%{params: %{in_reply_to_status_id: :deleted}} = draft) do
|
||||||
%__MODULE__{draft | in_reply_to: Activity.get_by_id(id)}
|
add_error(draft, dgettext("errors", "Cannot reply to a deleted status"))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp in_reply_to(%{params: %{in_reply_to_status_id: id} = params} = draft) when is_binary(id) do
|
||||||
|
activity = Activity.get_by_id(id)
|
||||||
|
|
||||||
|
params =
|
||||||
|
if is_nil(activity) do
|
||||||
|
# Deleted activities are returned as nil
|
||||||
|
Map.put(params, :in_reply_to_status_id, :deleted)
|
||||||
|
else
|
||||||
|
Map.put(params, :in_reply_to_status_id, activity)
|
||||||
|
end
|
||||||
|
|
||||||
|
in_reply_to(%{draft | params: params})
|
||||||
end
|
end
|
||||||
|
|
||||||
defp in_reply_to(%{params: %{in_reply_to_status_id: %Activity{} = in_reply_to}} = draft) do
|
defp in_reply_to(%{params: %{in_reply_to_status_id: %Activity{} = in_reply_to}} = draft) do
|
||||||
|
|
|
@ -44,7 +44,7 @@ def incoming_ap_doc(%{params: params, req_headers: req_headers}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def incoming_ap_doc(%{"type" => "Delete"} = params) do
|
def incoming_ap_doc(%{"type" => "Delete"} = params) do
|
||||||
ReceiverWorker.enqueue("incoming_ap_doc", %{"params" => params}, priority: 3)
|
ReceiverWorker.enqueue("incoming_ap_doc", %{"params" => params}, priority: 3, queue: :slow)
|
||||||
end
|
end
|
||||||
|
|
||||||
def incoming_ap_doc(params) do
|
def incoming_ap_doc(params) do
|
||||||
|
|
|
@ -152,6 +152,7 @@ def features do
|
||||||
|
|
||||||
def federation do
|
def federation do
|
||||||
quarantined = Config.get([:instance, :quarantined_instances], [])
|
quarantined = Config.get([:instance, :quarantined_instances], [])
|
||||||
|
rejected = Config.get([:instance, :rejected_instances], [])
|
||||||
|
|
||||||
if Config.get([:mrf, :transparency]) do
|
if Config.get([:mrf, :transparency]) do
|
||||||
{:ok, data} = MRF.describe()
|
{:ok, data} = MRF.describe()
|
||||||
|
@ -171,6 +172,12 @@ def federation do
|
||||||
|> Enum.map(fn {instance, reason} -> {instance, %{"reason" => reason}} end)
|
|> Enum.map(fn {instance, reason} -> {instance, %{"reason" => reason}} end)
|
||||||
|> Map.new()
|
|> Map.new()
|
||||||
})
|
})
|
||||||
|
|> Map.put(
|
||||||
|
:rejected_instances,
|
||||||
|
rejected
|
||||||
|
|> Enum.map(fn {instance, reason} -> {instance, %{"reason" => reason}} end)
|
||||||
|
|> Map.new()
|
||||||
|
)
|
||||||
else
|
else
|
||||||
%{}
|
%{}
|
||||||
end
|
end
|
||||||
|
|
|
@ -624,6 +624,19 @@ def render("attachment.json", %{attachment: attachment}) do
|
||||||
to_string(attachment["id"] || hash_id)
|
to_string(attachment["id"] || hash_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
description =
|
||||||
|
if attachment["summary"] do
|
||||||
|
HTML.strip_tags(attachment["summary"])
|
||||||
|
else
|
||||||
|
attachment["name"]
|
||||||
|
end
|
||||||
|
|
||||||
|
name = if attachment["summary"], do: attachment["name"]
|
||||||
|
|
||||||
|
pleroma =
|
||||||
|
%{mime_type: media_type}
|
||||||
|
|> Maps.put_if_present(:name, name)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
id: attachment_id,
|
id: attachment_id,
|
||||||
url: href,
|
url: href,
|
||||||
|
@ -631,8 +644,8 @@ def render("attachment.json", %{attachment: attachment}) do
|
||||||
preview_url: href_preview,
|
preview_url: href_preview,
|
||||||
text_url: href,
|
text_url: href,
|
||||||
type: type,
|
type: type,
|
||||||
description: attachment["name"],
|
description: description,
|
||||||
pleroma: %{mime_type: media_type},
|
pleroma: pleroma,
|
||||||
blurhash: attachment["blurhash"]
|
blurhash: attachment["blurhash"]
|
||||||
}
|
}
|
||||||
|> Maps.put_if_present(:meta, meta)
|
|> Maps.put_if_present(:meta, meta)
|
||||||
|
|
|
@ -54,9 +54,10 @@ def preview(%Conn{} = conn, %{"sig" => sig64, "url" => url64}) do
|
||||||
|
|
||||||
defp handle_preview(conn, url) do
|
defp handle_preview(conn, url) do
|
||||||
media_proxy_url = MediaProxy.url(url)
|
media_proxy_url = MediaProxy.url(url)
|
||||||
|
http_client_opts = Pleroma.Config.get([:media_proxy, :proxy_opts, :http], pool: :media)
|
||||||
|
|
||||||
with {:ok, %{status: status} = head_response} when status in 200..299 <-
|
with {:ok, %{status: status} = head_response} when status in 200..299 <-
|
||||||
Pleroma.HTTP.request(:head, media_proxy_url, "", [], pool: :media) do
|
Pleroma.HTTP.request(:head, media_proxy_url, "", [], http_client_opts) do
|
||||||
content_type = Tesla.get_header(head_response, "content-type")
|
content_type = Tesla.get_header(head_response, "content-type")
|
||||||
content_length = Tesla.get_header(head_response, "content-length")
|
content_length = Tesla.get_header(head_response, "content-length")
|
||||||
content_length = content_length && String.to_integer(content_length)
|
content_length = content_length && String.to_integer(content_length)
|
||||||
|
|
|
@ -96,7 +96,7 @@ defp put_valid_until(changeset, attrs) do
|
||||||
|> validate_required([:valid_until])
|
|> validate_required([:valid_until])
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec create(App.t(), User.t(), map()) :: {:ok, Token} | {:error, Ecto.Changeset.t()}
|
@spec create(App.t(), User.t(), map()) :: {:ok, Token.t()} | {:error, Ecto.Changeset.t()}
|
||||||
def create(%App{} = app, %User{} = user, attrs \\ %{}) do
|
def create(%App{} = app, %User{} = user, attrs \\ %{}) do
|
||||||
with {:ok, token} <- do_create(app, user, attrs) do
|
with {:ok, token} <- do_create(app, user, attrs) do
|
||||||
if Pleroma.Config.get([:oauth2, :clean_expired_tokens]) do
|
if Pleroma.Config.get([:oauth2, :clean_expired_tokens]) do
|
||||||
|
|
|
@ -3,26 +3,27 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do
|
defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do
|
||||||
alias Pleroma.Config
|
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
@config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config)
|
||||||
|
|
||||||
def init(opts), do: opts
|
def init(opts), do: opts
|
||||||
|
|
||||||
def call(conn, _options) do
|
def call(conn, _options) do
|
||||||
if Config.get([:http_security, :enabled]) do
|
if @config_impl.get([:http_security, :enabled]) do
|
||||||
conn
|
conn
|
||||||
|> merge_resp_headers(headers())
|
|> merge_resp_headers(headers())
|
||||||
|> maybe_send_sts_header(Config.get([:http_security, :sts]))
|
|> maybe_send_sts_header(@config_impl.get([:http_security, :sts]))
|
||||||
else
|
else
|
||||||
conn
|
conn
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def primary_frontend do
|
def primary_frontend do
|
||||||
with %{"name" => frontend} <- Config.get([:frontends, :primary]),
|
with %{"name" => frontend} <- @config_impl.get([:frontends, :primary]),
|
||||||
available <- Config.get([:frontends, :available]),
|
available <- @config_impl.get([:frontends, :available]),
|
||||||
%{} = primary_frontend <- Map.get(available, frontend) do
|
%{} = primary_frontend <- Map.get(available, frontend) do
|
||||||
{:ok, primary_frontend}
|
{:ok, primary_frontend}
|
||||||
end
|
end
|
||||||
|
@ -37,8 +38,8 @@ def custom_http_frontend_headers do
|
||||||
end
|
end
|
||||||
|
|
||||||
def headers do
|
def headers do
|
||||||
referrer_policy = Config.get([:http_security, :referrer_policy])
|
referrer_policy = @config_impl.get([:http_security, :referrer_policy])
|
||||||
report_uri = Config.get([:http_security, :report_uri])
|
report_uri = @config_impl.get([:http_security, :report_uri])
|
||||||
custom_http_frontend_headers = custom_http_frontend_headers()
|
custom_http_frontend_headers = custom_http_frontend_headers()
|
||||||
|
|
||||||
headers = [
|
headers = [
|
||||||
|
@ -86,10 +87,10 @@ def headers do
|
||||||
@csp_start [Enum.join(static_csp_rules, ";") <> ";"]
|
@csp_start [Enum.join(static_csp_rules, ";") <> ";"]
|
||||||
|
|
||||||
defp csp_string do
|
defp csp_string do
|
||||||
scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
|
scheme = @config_impl.get([Pleroma.Web.Endpoint, :url])[:scheme]
|
||||||
static_url = Pleroma.Web.Endpoint.static_url()
|
static_url = Pleroma.Web.Endpoint.static_url()
|
||||||
websocket_url = Pleroma.Web.Endpoint.websocket_url()
|
websocket_url = Pleroma.Web.Endpoint.websocket_url()
|
||||||
report_uri = Config.get([:http_security, :report_uri])
|
report_uri = @config_impl.get([:http_security, :report_uri])
|
||||||
|
|
||||||
img_src = "img-src 'self' data: blob:"
|
img_src = "img-src 'self' data: blob:"
|
||||||
media_src = "media-src 'self'"
|
media_src = "media-src 'self'"
|
||||||
|
@ -97,8 +98,8 @@ defp csp_string do
|
||||||
|
|
||||||
# Strict multimedia CSP enforcement only when MediaProxy is enabled
|
# Strict multimedia CSP enforcement only when MediaProxy is enabled
|
||||||
{img_src, media_src, connect_src} =
|
{img_src, media_src, connect_src} =
|
||||||
if Config.get([:media_proxy, :enabled]) &&
|
if @config_impl.get([:media_proxy, :enabled]) &&
|
||||||
!Config.get([:media_proxy, :proxy_opts, :redirect_on_failure]) do
|
!@config_impl.get([:media_proxy, :proxy_opts, :redirect_on_failure]) do
|
||||||
sources = build_csp_multimedia_source_list()
|
sources = build_csp_multimedia_source_list()
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -115,18 +116,22 @@ defp csp_string do
|
||||||
end
|
end
|
||||||
|
|
||||||
connect_src =
|
connect_src =
|
||||||
if Config.get(:env) == :dev do
|
if @config_impl.get([:env]) == :dev do
|
||||||
[connect_src, " http://localhost:3035/"]
|
[connect_src, " http://localhost:3035/"]
|
||||||
else
|
else
|
||||||
connect_src
|
connect_src
|
||||||
end
|
end
|
||||||
|
|
||||||
script_src =
|
script_src =
|
||||||
if Config.get(:env) == :dev do
|
if @config_impl.get([:http_security, :allow_unsafe_eval]) do
|
||||||
|
if @config_impl.get([:env]) == :dev do
|
||||||
"script-src 'self' 'unsafe-eval'"
|
"script-src 'self' 'unsafe-eval'"
|
||||||
else
|
else
|
||||||
"script-src 'self' 'wasm-unsafe-eval'"
|
"script-src 'self' 'wasm-unsafe-eval'"
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
"script-src 'self'"
|
||||||
|
end
|
||||||
|
|
||||||
report = if report_uri, do: ["report-uri ", report_uri, ";report-to csp-endpoint"]
|
report = if report_uri, do: ["report-uri ", report_uri, ";report-to csp-endpoint"]
|
||||||
insecure = if scheme == "https", do: "upgrade-insecure-requests"
|
insecure = if scheme == "https", do: "upgrade-insecure-requests"
|
||||||
|
@ -161,11 +166,11 @@ defp build_csp_param_from_whitelist(url), do: url
|
||||||
defp build_csp_multimedia_source_list do
|
defp build_csp_multimedia_source_list do
|
||||||
media_proxy_whitelist =
|
media_proxy_whitelist =
|
||||||
[:media_proxy, :whitelist]
|
[:media_proxy, :whitelist]
|
||||||
|> Config.get()
|
|> @config_impl.get()
|
||||||
|> build_csp_from_whitelist([])
|
|> build_csp_from_whitelist([])
|
||||||
|
|
||||||
captcha_method = Config.get([Pleroma.Captcha, :method])
|
captcha_method = @config_impl.get([Pleroma.Captcha, :method])
|
||||||
captcha_endpoint = Config.get([captcha_method, :endpoint])
|
captcha_endpoint = @config_impl.get([captcha_method, :endpoint])
|
||||||
|
|
||||||
base_endpoints =
|
base_endpoints =
|
||||||
[
|
[
|
||||||
|
@ -173,7 +178,7 @@ defp build_csp_multimedia_source_list do
|
||||||
[Pleroma.Upload, :base_url],
|
[Pleroma.Upload, :base_url],
|
||||||
[Pleroma.Uploaders.S3, :public_endpoint]
|
[Pleroma.Uploaders.S3, :public_endpoint]
|
||||||
]
|
]
|
||||||
|> Enum.map(&Config.get/1)
|
|> Enum.map(&@config_impl.get/1)
|
||||||
|
|
||||||
[captcha_endpoint | base_endpoints]
|
[captcha_endpoint | base_endpoints]
|
||||||
|> Enum.map(&build_csp_param/1)
|
|> Enum.map(&build_csp_param/1)
|
||||||
|
@ -200,7 +205,7 @@ defp build_csp_param(url) when is_binary(url) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def warn_if_disabled do
|
def warn_if_disabled do
|
||||||
unless Config.get([:http_security, :enabled]) do
|
unless Pleroma.Config.get([:http_security, :enabled]) do
|
||||||
Logger.warning("
|
Logger.warning("
|
||||||
.i;;;;i.
|
.i;;;;i.
|
||||||
iYcviii;vXY:
|
iYcviii;vXY:
|
||||||
|
@ -245,8 +250,8 @@ def warn_if_disabled do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_send_sts_header(conn, true) do
|
defp maybe_send_sts_header(conn, true) do
|
||||||
max_age_sts = Config.get([:http_security, :sts_max_age])
|
max_age_sts = @config_impl.get([:http_security, :sts_max_age])
|
||||||
max_age_ct = Config.get([:http_security, :ct_max_age])
|
max_age_ct = @config_impl.get([:http_security, :ct_max_age])
|
||||||
|
|
||||||
merge_resp_headers(conn, [
|
merge_resp_headers(conn, [
|
||||||
{"strict-transport-security", "max-age=#{max_age_sts}; includeSubDomains"},
|
{"strict-transport-security", "max-age=#{max_age_sts}; includeSubDomains"},
|
||||||
|
|
|
@ -3,10 +3,22 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
|
defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
|
||||||
|
alias Pleroma.Helpers.InetHelper
|
||||||
|
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
import Phoenix.Controller, only: [get_format: 1, text: 2]
|
import Phoenix.Controller, only: [get_format: 1, text: 2]
|
||||||
|
|
||||||
|
alias Pleroma.Web.ActivityPub.MRF
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
@config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config)
|
||||||
|
@http_signatures_impl Application.compile_env(
|
||||||
|
:pleroma,
|
||||||
|
[__MODULE__, :http_signatures_impl],
|
||||||
|
HTTPSignatures
|
||||||
|
)
|
||||||
|
|
||||||
def init(options) do
|
def init(options) do
|
||||||
options
|
options
|
||||||
end
|
end
|
||||||
|
@ -19,7 +31,9 @@ def call(conn, _opts) do
|
||||||
if get_format(conn) in ["json", "activity+json"] do
|
if get_format(conn) in ["json", "activity+json"] do
|
||||||
conn
|
conn
|
||||||
|> maybe_assign_valid_signature()
|
|> maybe_assign_valid_signature()
|
||||||
|
|> maybe_assign_actor_id()
|
||||||
|> maybe_require_signature()
|
|> maybe_require_signature()
|
||||||
|
|> maybe_filter_requests()
|
||||||
else
|
else
|
||||||
conn
|
conn
|
||||||
end
|
end
|
||||||
|
@ -33,7 +47,7 @@ defp validate_signature(conn, request_target) do
|
||||||
|> put_req_header("(request-target)", request_target)
|
|> put_req_header("(request-target)", request_target)
|
||||||
|> put_req_header("@request-target", request_target)
|
|> put_req_header("@request-target", request_target)
|
||||||
|
|
||||||
HTTPSignatures.validate_conn(conn)
|
@http_signatures_impl.validate_conn(conn)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp validate_signature(conn) do
|
defp validate_signature(conn) do
|
||||||
|
@ -83,20 +97,63 @@ defp maybe_assign_valid_signature(conn) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp maybe_assign_actor_id(%{assigns: %{valid_signature: true}} = conn) do
|
||||||
|
adapter = Application.get_env(:http_signatures, :adapter)
|
||||||
|
|
||||||
|
{:ok, actor_id} = adapter.get_actor_id(conn)
|
||||||
|
|
||||||
|
assign(conn, :actor_id, actor_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_assign_actor_id(conn), do: conn
|
||||||
|
|
||||||
defp has_signature_header?(conn) do
|
defp has_signature_header?(conn) do
|
||||||
conn |> get_req_header("signature") |> Enum.at(0, false)
|
conn |> get_req_header("signature") |> Enum.at(0, false)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_require_signature(%{assigns: %{valid_signature: true}} = conn), do: conn
|
defp maybe_require_signature(%{assigns: %{valid_signature: true}} = conn), do: conn
|
||||||
|
|
||||||
defp maybe_require_signature(conn) do
|
defp maybe_require_signature(%{remote_ip: remote_ip} = conn) do
|
||||||
if Pleroma.Config.get([:activitypub, :authorized_fetch_mode], false) do
|
if @config_impl.get([:activitypub, :authorized_fetch_mode], false) do
|
||||||
|
exceptions =
|
||||||
|
@config_impl.get([:activitypub, :authorized_fetch_mode_exceptions], [])
|
||||||
|
|> Enum.map(&InetHelper.parse_cidr/1)
|
||||||
|
|
||||||
|
if Enum.any?(exceptions, fn x -> InetCidr.contains?(x, remote_ip) end) do
|
||||||
|
conn
|
||||||
|
else
|
||||||
conn
|
conn
|
||||||
|> put_status(:unauthorized)
|
|> put_status(:unauthorized)
|
||||||
|> text("Request not signed")
|
|> text("Request not signed")
|
||||||
|> halt()
|
|> halt()
|
||||||
|
end
|
||||||
else
|
else
|
||||||
conn
|
conn
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp maybe_filter_requests(%{halted: true} = conn), do: conn
|
||||||
|
|
||||||
|
defp maybe_filter_requests(conn) do
|
||||||
|
if @config_impl.get([:activitypub, :authorized_fetch_mode], false) and
|
||||||
|
conn.assigns[:actor_id] do
|
||||||
|
%{host: host} = URI.parse(conn.assigns.actor_id)
|
||||||
|
|
||||||
|
if MRF.subdomain_match?(rejected_domains(), host) do
|
||||||
|
conn
|
||||||
|
|> put_status(:unauthorized)
|
||||||
|
|> halt()
|
||||||
|
else
|
||||||
|
conn
|
||||||
|
end
|
||||||
|
else
|
||||||
|
conn
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp rejected_domains do
|
||||||
|
@config_impl.get([:instance, :rejected_instances])
|
||||||
|
|> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples()
|
||||||
|
|> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.Plugs.RemoteIp do
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.Helpers.InetHelper
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
|
|
||||||
@behaviour Plug
|
@behaviour Plug
|
||||||
|
@ -30,19 +31,8 @@ defp remote_ip_opts do
|
||||||
proxies =
|
proxies =
|
||||||
Config.get([__MODULE__, :proxies], [])
|
Config.get([__MODULE__, :proxies], [])
|
||||||
|> Enum.concat(reserved)
|
|> Enum.concat(reserved)
|
||||||
|> Enum.map(&maybe_add_cidr/1)
|
|> Enum.map(&InetHelper.parse_cidr/1)
|
||||||
|
|
||||||
{headers, proxies}
|
{headers, proxies}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_add_cidr(proxy) when is_binary(proxy) do
|
|
||||||
proxy =
|
|
||||||
cond do
|
|
||||||
"/" in String.codepoints(proxy) -> proxy
|
|
||||||
InetCidr.v4?(InetCidr.parse_address!(proxy)) -> proxy <> "/32"
|
|
||||||
InetCidr.v6?(InetCidr.parse_address!(proxy)) -> proxy <> "/128"
|
|
||||||
end
|
|
||||||
|
|
||||||
InetCidr.parse_cidr!(proxy, true)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -63,6 +63,7 @@ def perform(_) do
|
||||||
|
|
||||||
@doc "Push message to web"
|
@doc "Push message to web"
|
||||||
def push_message(body, sub, api_key, subscription) do
|
def push_message(body, sub, api_key, subscription) do
|
||||||
|
try do
|
||||||
case WebPushEncryption.send_web_push(body, sub, api_key) do
|
case WebPushEncryption.send_web_push(body, sub, api_key) do
|
||||||
{:ok, %{status: code}} when code in 400..499 ->
|
{:ok, %{status: code}} when code in 400..499 ->
|
||||||
Logger.debug("Removing subscription record")
|
Logger.debug("Removing subscription record")
|
||||||
|
@ -76,6 +77,11 @@ def push_message(body, sub, api_key, subscription) do
|
||||||
Logger.error("Web Push Notification failed with code: #{code}")
|
Logger.error("Web Push Notification failed with code: #{code}")
|
||||||
:error
|
:error
|
||||||
|
|
||||||
|
error ->
|
||||||
|
Logger.error("Web Push Notification failed with #{inspect(error)}")
|
||||||
|
:error
|
||||||
|
end
|
||||||
|
rescue
|
||||||
error ->
|
error ->
|
||||||
Logger.error("Web Push Notification failed with #{inspect(error)}")
|
Logger.error("Web Push Notification failed with #{inspect(error)}")
|
||||||
:error
|
:error
|
||||||
|
|
|
@ -58,7 +58,7 @@ defp check_content_length(headers) do
|
||||||
|
|
||||||
defp http_options do
|
defp http_options do
|
||||||
[
|
[
|
||||||
pool: :media,
|
pool: :rich_media,
|
||||||
max_body: Config.get([:rich_media, :max_body], 5_000_000)
|
max_body: Config.get([:rich_media, :max_body], 5_000_000)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,7 +23,7 @@ defp aws_signed_url?(image) when is_binary(image) and image != "" do
|
||||||
%URI{host: host, query: query} = URI.parse(image)
|
%URI{host: host, query: query} = URI.parse(image)
|
||||||
|
|
||||||
is_binary(host) and String.contains?(host, "amazonaws.com") and
|
is_binary(host) and String.contains?(host, "amazonaws.com") and
|
||||||
String.contains?(query, "X-Amz-Expires")
|
is_binary(query) and String.contains?(query, "X-Amz-Expires")
|
||||||
end
|
end
|
||||||
|
|
||||||
defp aws_signed_url?(_), do: nil
|
defp aws_signed_url?(_), do: nil
|
||||||
|
|
|
@ -8,7 +8,7 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
|
||||||
use Pleroma.Workers.WorkerHelper, queue: "attachments_cleanup"
|
use Pleroma.Workers.WorkerHelper, queue: "slow"
|
||||||
|
|
||||||
@impl Oban.Worker
|
@impl Oban.Worker
|
||||||
def perform(%Job{
|
def perform(%Job{
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Workers.BackupWorker do
|
defmodule Pleroma.Workers.BackupWorker do
|
||||||
use Oban.Worker, queue: :backup, max_attempts: 1
|
use Oban.Worker, queue: :slow, max_attempts: 1
|
||||||
|
|
||||||
alias Oban.Job
|
alias Oban.Job
|
||||||
alias Pleroma.User.Backup
|
alias Pleroma.User.Backup
|
||||||
|
|
|
@ -9,7 +9,7 @@ defmodule Pleroma.Workers.Cron.NewUsersDigestWorker do
|
||||||
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
use Pleroma.Workers.WorkerHelper, queue: "mailer"
|
use Pleroma.Workers.WorkerHelper, queue: "background"
|
||||||
|
|
||||||
@impl Oban.Worker
|
@impl Oban.Worker
|
||||||
def perform(_job) do
|
def perform(_job) do
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Workers.MailerWorker do
|
defmodule Pleroma.Workers.MailerWorker do
|
||||||
use Pleroma.Workers.WorkerHelper, queue: "mailer"
|
use Pleroma.Workers.WorkerHelper, queue: "background"
|
||||||
|
|
||||||
@impl Oban.Worker
|
@impl Oban.Worker
|
||||||
def perform(%Job{args: %{"op" => "email", "encoded_email" => encoded_email, "config" => config}}) do
|
def perform(%Job{args: %{"op" => "email", "encoded_email" => encoded_email, "config" => config}}) do
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Workers.MuteExpireWorker do
|
defmodule Pleroma.Workers.MuteExpireWorker do
|
||||||
use Pleroma.Workers.WorkerHelper, queue: "mute_expire"
|
use Pleroma.Workers.WorkerHelper, queue: "background"
|
||||||
|
|
||||||
@impl Oban.Worker
|
@impl Oban.Worker
|
||||||
def perform(%Job{args: %{"op" => "unmute_user", "muter_id" => muter_id, "mutee_id" => mutee_id}}) do
|
def perform(%Job{args: %{"op" => "unmute_user", "muter_id" => muter_id, "mutee_id" => mutee_id}}) do
|
||||||
|
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.Workers.PollWorker do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Generates notifications when a poll ends.
|
Generates notifications when a poll ends.
|
||||||
"""
|
"""
|
||||||
use Pleroma.Workers.WorkerHelper, queue: "poll_notifications"
|
use Pleroma.Workers.WorkerHelper, queue: "background"
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
|
|
|
@ -7,7 +7,7 @@ defmodule Pleroma.Workers.PurgeExpiredActivity do
|
||||||
Worker which purges expired activity.
|
Worker which purges expired activity.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
use Oban.Worker, queue: :activity_expiration, max_attempts: 1, unique: [period: :infinity]
|
use Oban.Worker, queue: :slow, max_attempts: 1, unique: [period: :infinity]
|
||||||
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ defp find_user(ap_id) do
|
||||||
def get_expiration(id) do
|
def get_expiration(id) do
|
||||||
from(j in Oban.Job,
|
from(j in Oban.Job,
|
||||||
where: j.state == "scheduled",
|
where: j.state == "scheduled",
|
||||||
where: j.queue == "activity_expiration",
|
where: j.queue == "slow",
|
||||||
where: fragment("?->>'activity_id' = ?", j.args, ^id)
|
where: fragment("?->>'activity_id' = ?", j.args, ^id)
|
||||||
)
|
)
|
||||||
|> Pleroma.Repo.one()
|
|> Pleroma.Repo.one()
|
||||||
|
|
|
@ -7,7 +7,7 @@ defmodule Pleroma.Workers.PurgeExpiredFilter do
|
||||||
Worker which purges expired filters
|
Worker which purges expired filters
|
||||||
"""
|
"""
|
||||||
|
|
||||||
use Oban.Worker, queue: :filter_expiration, max_attempts: 1, unique: [period: :infinity]
|
use Oban.Worker, queue: :background, max_attempts: 1, unique: [period: :infinity]
|
||||||
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ def timeout(_job), do: :timer.seconds(5)
|
||||||
def get_expiration(id) do
|
def get_expiration(id) do
|
||||||
from(j in Job,
|
from(j in Job,
|
||||||
where: j.state == "scheduled",
|
where: j.state == "scheduled",
|
||||||
where: j.queue == "filter_expiration",
|
where: j.queue == "background",
|
||||||
where: fragment("?->'filter_id' = ?", j.args, ^id)
|
where: fragment("?->'filter_id' = ?", j.args, ^id)
|
||||||
)
|
)
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
|
|
|
@ -7,7 +7,7 @@ defmodule Pleroma.Workers.PurgeExpiredToken do
|
||||||
Worker which purges expired OAuth tokens
|
Worker which purges expired OAuth tokens
|
||||||
"""
|
"""
|
||||||
|
|
||||||
use Oban.Worker, queue: :token_expiration, max_attempts: 1
|
use Oban.Worker, queue: :background, max_attempts: 1
|
||||||
|
|
||||||
@spec enqueue(%{token_id: integer(), valid_until: DateTime.t(), mod: module()}) ::
|
@spec enqueue(%{token_id: integer(), valid_until: DateTime.t(), mod: module()}) ::
|
||||||
{:ok, Oban.Job.t()} | {:error, Ecto.Changeset.t()}
|
{:ok, Oban.Job.t()} | {:error, Ecto.Changeset.t()}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
defmodule Pleroma.Workers.RemoteFetcherWorker do
|
defmodule Pleroma.Workers.RemoteFetcherWorker do
|
||||||
alias Pleroma.Object.Fetcher
|
alias Pleroma.Object.Fetcher
|
||||||
|
|
||||||
use Pleroma.Workers.WorkerHelper, queue: "remote_fetcher"
|
use Pleroma.Workers.WorkerHelper, queue: "background"
|
||||||
|
|
||||||
@impl Oban.Worker
|
@impl Oban.Worker
|
||||||
def perform(%Job{args: %{"op" => "fetch_remote", "id" => id} = args}) do
|
def perform(%Job{args: %{"op" => "fetch_remote", "id" => id} = args}) do
|
||||||
|
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.Workers.RichMediaExpirationWorker do
|
||||||
alias Pleroma.Web.RichMedia.Card
|
alias Pleroma.Web.RichMedia.Card
|
||||||
|
|
||||||
use Oban.Worker,
|
use Oban.Worker,
|
||||||
queue: :rich_media_expiration
|
queue: :background
|
||||||
|
|
||||||
@impl Oban.Worker
|
@impl Oban.Worker
|
||||||
def perform(%Job{args: %{"url" => url} = _args}) do
|
def perform(%Job{args: %{"url" => url} = _args}) do
|
||||||
|
|
|
@ -7,7 +7,7 @@ defmodule Pleroma.Workers.ScheduledActivityWorker do
|
||||||
The worker to post scheduled activity.
|
The worker to post scheduled activity.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
use Pleroma.Workers.WorkerHelper, queue: "scheduled_activities"
|
use Pleroma.Workers.WorkerHelper, queue: "federator_outgoing"
|
||||||
|
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.ScheduledActivity
|
alias Pleroma.ScheduledActivity
|
||||||
|
|
5
mix.exs
5
mix.exs
|
@ -5,7 +5,7 @@ def project do
|
||||||
[
|
[
|
||||||
app: :pleroma,
|
app: :pleroma,
|
||||||
version: version("2.6.52"),
|
version: version("2.6.52"),
|
||||||
elixir: "~> 1.11",
|
elixir: "~> 1.13",
|
||||||
elixirc_paths: elixirc_paths(Mix.env()),
|
elixirc_paths: elixirc_paths(Mix.env()),
|
||||||
compilers: Mix.compilers(),
|
compilers: Mix.compilers(),
|
||||||
elixirc_options: [warnings_as_errors: warnings_as_errors()],
|
elixirc_options: [warnings_as_errors: warnings_as_errors()],
|
||||||
|
@ -188,7 +188,8 @@ defp deps do
|
||||||
{:exile,
|
{:exile,
|
||||||
git: "https://github.com/akash-akya/exile.git",
|
git: "https://github.com/akash-akya/exile.git",
|
||||||
ref: "be87c33b02a7c3c5d22d2ece01fbd462355b28ef"},
|
ref: "be87c33b02a7c3c5d22d2ece01fbd462355b28ef"},
|
||||||
{:bandit, "~> 1.2"},
|
{:bandit, "~> 1.5.2"},
|
||||||
|
{:websock_adapter, "~> 0.5.6"},
|
||||||
|
|
||||||
## dev & test
|
## dev & test
|
||||||
{:ex_doc, "~> 0.22", only: :dev, runtime: false},
|
{:ex_doc, "~> 0.22", only: :dev, runtime: false},
|
||||||
|
|
16
mix.lock
16
mix.lock
|
@ -1,6 +1,6 @@
|
||||||
%{
|
%{
|
||||||
"accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"},
|
"accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"},
|
||||||
"bandit": {:hex, :bandit, "1.2.1", "aa485b4ac175065b8e0fb5864ddd5dd7b50d52336b36f61c82f484c3718b3d15", [:mix], [{:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "27393e590a407f1b7d51c5fee4737f139fe224a30449ce25061eac70f763896b"},
|
"bandit": {:hex, :bandit, "1.5.2", "ed0a41c43a9e529c670d0fd48371db4027e7b80d43b1942893e17deb8bed0540", [:mix], [{:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "35ddbdce7e8a2a3c6b5093f7299d70832a43ed2f4a1852885a61d334cab1b4ad"},
|
||||||
"base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"},
|
"base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"},
|
||||||
"bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"},
|
"bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"},
|
||||||
"bcrypt_elixir": {:hex, :bcrypt_elixir, "2.3.1", "5114d780459a04f2b4aeef52307de23de961b69e13a5cd98a911e39fda13f420", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "42182d5f46764def15bf9af83739e3bf4ad22661b1c34fc3e88558efced07279"},
|
"bcrypt_elixir": {:hex, :bcrypt_elixir, "2.3.1", "5114d780459a04f2b4aeef52307de23de961b69e13a5cd98a911e39fda13f420", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "42182d5f46764def15bf9af83739e3bf4ad22661b1c34fc3e88558efced07279"},
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
|
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
|
||||||
"cors_plug": {:hex, :cors_plug, "2.0.3", "316f806d10316e6d10f09473f19052d20ba0a0ce2a1d910ddf57d663dac402ae", [:mix], [{:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ee4ae1418e6ce117fc42c2ba3e6cbdca4e95ecd2fe59a05ec6884ca16d469aea"},
|
"cors_plug": {:hex, :cors_plug, "2.0.3", "316f806d10316e6d10f09473f19052d20ba0a0ce2a1d910ddf57d663dac402ae", [:mix], [{:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ee4ae1418e6ce117fc42c2ba3e6cbdca4e95ecd2fe59a05ec6884ca16d469aea"},
|
||||||
"covertool": {:hex, :covertool, "2.0.6", "4a291b4e3449025b0595d8f44c8d7635d4f48f033be2ce88d22a329f36f94a91", [:rebar3], [], "hexpm", "5db3fcd82180d8ea4ad857d4d1ab21a8d31b5aee0d60d2f6c0f9e25a411d1e21"},
|
"covertool": {:hex, :covertool, "2.0.6", "4a291b4e3449025b0595d8f44c8d7635d4f48f033be2ce88d22a329f36f94a91", [:rebar3], [], "hexpm", "5db3fcd82180d8ea4ad857d4d1ab21a8d31b5aee0d60d2f6c0f9e25a411d1e21"},
|
||||||
"cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"},
|
"cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"},
|
||||||
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
|
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
|
||||||
"cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"},
|
"cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"},
|
||||||
"credo": {:hex, :credo, "1.7.3", "05bb11eaf2f2b8db370ecaa6a6bda2ec49b2acd5e0418bc106b73b07128c0436", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "35ea675a094c934c22fb1dca3696f3c31f2728ae6ef5a53b5d648c11180a4535"},
|
"credo": {:hex, :credo, "1.7.3", "05bb11eaf2f2b8db370ecaa6a6bda2ec49b2acd5e0418bc106b73b07128c0436", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "35ea675a094c934c22fb1dca3696f3c31f2728ae6ef5a53b5d648c11180a4535"},
|
||||||
"crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
|
"crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
|
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
|
||||||
|
@ -102,9 +102,9 @@
|
||||||
"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_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_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.3", "4d32c4817fce933693741deeb99ef1392619f942633dde834a5163124813aad3", [: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", "cd34049af41be2c627df99cd4eaa71fc52a328c0c3d8e7d4aa28f880c30e7f64"},
|
"phoenix_view": {:hex, :phoenix_view, "2.0.3", "4d32c4817fce933693741deeb99ef1392619f942633dde834a5163124813aad3", [: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", "cd34049af41be2c627df99cd4eaa71fc52a328c0c3d8e7d4aa28f880c30e7f64"},
|
||||||
"plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [: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", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"},
|
"plug": {:hex, :plug, "1.16.0", "1d07d50cb9bb05097fdf187b31cf087c7297aafc3fed8299aac79c128a707e47", [: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", "cbf53aa1f5c4d758a7559c0bd6d59e286c2be0c6a1fac8cc3eee2f638243b93e"},
|
||||||
"plug_cowboy": {:hex, :plug_cowboy, "2.6.2", "753611b23b29231fb916b0cdd96028084b12aff57bfd7b71781bd04b1dbeb5c9", [: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", "951ed2433df22f4c97b85fdb145d4cee561f36b74854d64c06d896d7cd2921a7"},
|
"plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [: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", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"},
|
||||||
"plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"},
|
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
|
||||||
"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"},
|
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"},
|
||||||
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"},
|
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"},
|
||||||
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
|
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
|
||||||
|
@ -134,7 +134,7 @@
|
||||||
"telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.2.0", "b583c3f18508f5c5561b674d16cf5d9afd2ea3c04505b7d92baaeac93c1b8260", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "9cba950e1c4733468efbe3f821841f34ac05d28e7af7798622f88ecdbbe63ea3"},
|
"telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.2.0", "b583c3f18508f5c5561b674d16cf5d9afd2ea3c04505b7d92baaeac93c1b8260", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "9cba950e1c4733468efbe3f821841f34ac05d28e7af7798622f88ecdbbe63ea3"},
|
||||||
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
|
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
|
||||||
"tesla": {:hex, :tesla, "1.8.0", "d511a4f5c5e42538d97eef7c40ec4f3e44effdc5068206f42ed859e09e51d1fd", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "10501f360cd926a309501287470372af1a6e1cbed0f43949203a4c13300bc79f"},
|
"tesla": {:hex, :tesla, "1.8.0", "d511a4f5c5e42538d97eef7c40ec4f3e44effdc5068206f42ed859e09e51d1fd", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "10501f360cd926a309501287470372af1a6e1cbed0f43949203a4c13300bc79f"},
|
||||||
"thousand_island": {:hex, :thousand_island, "1.3.2", "bc27f9afba6e1a676dd36507d42e429935a142cf5ee69b8e3f90bff1383943cd", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0e085b93012cd1057b378fce40cbfbf381ff6d957a382bfdd5eca1a98eec2535"},
|
"thousand_island": {:hex, :thousand_island, "1.3.5", "6022b6338f1635b3d32406ff98d68b843ba73b3aa95cfc27154223244f3a6ca5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2be6954916fdfe4756af3239fb6b6d75d0b8063b5df03ba76fd8a4c87849e180"},
|
||||||
"timex": {:hex, :timex, "3.7.7", "3ed093cae596a410759104d878ad7b38e78b7c2151c6190340835515d4a46b8a", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "0ec4b09f25fe311321f9fc04144a7e3affe48eb29481d7a5583849b6c4dfa0a7"},
|
"timex": {:hex, :timex, "3.7.7", "3ed093cae596a410759104d878ad7b38e78b7c2151c6190340835515d4a46b8a", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "0ec4b09f25fe311321f9fc04144a7e3affe48eb29481d7a5583849b6c4dfa0a7"},
|
||||||
"toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"},
|
"toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"},
|
||||||
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
|
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
|
||||||
|
@ -145,6 +145,6 @@
|
||||||
"vix": {:hex, :vix, "0.26.0", "027f10b6969b759318be84bd0bd8c88af877445e4e41cf96a0460392cea5399c", [:make, :mix], [{:castore, "~> 1.0 or ~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:cc_precompiler, "~> 0.2 or ~> 0.1.4", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.8 or ~> 0.7.3", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}], "hexpm", "71b0a79ae7f199cacfc8e679b0e4ba25ee47dc02e182c5b9097efb29fbe14efd"},
|
"vix": {:hex, :vix, "0.26.0", "027f10b6969b759318be84bd0bd8c88af877445e4e41cf96a0460392cea5399c", [:make, :mix], [{:castore, "~> 1.0 or ~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:cc_precompiler, "~> 0.2 or ~> 0.1.4", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.8 or ~> 0.7.3", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}], "hexpm", "71b0a79ae7f199cacfc8e679b0e4ba25ee47dc02e182c5b9097efb29fbe14efd"},
|
||||||
"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"},
|
"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": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
|
||||||
"websock_adapter": {:hex, :websock_adapter, "0.5.5", "9dfeee8269b27e958a65b3e235b7e447769f66b5b5925385f5a569269164a210", [: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", "4b977ba4a01918acbf77045ff88de7f6972c2a009213c515a445c48f224ffce9"},
|
"websock_adapter": {:hex, :websock_adapter, "0.5.6", "0437fe56e093fd4ac422de33bf8fc89f7bc1416a3f2d732d8b2c8fd54792fe60", [: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", "e04378d26b0af627817ae84c92083b7e97aca3121196679b73c73b99d0d133ea"},
|
||||||
"websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"},
|
"websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"},
|
||||||
}
|
}
|
||||||
|
|
|
@ -5973,3 +5973,87 @@ msgstr ""
|
||||||
msgctxt "config label at :pleroma-:instance > :languages"
|
msgctxt "config label at :pleroma-:instance > :languages"
|
||||||
msgid "Languages"
|
msgid "Languages"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/pleroma/docs/translator.ex:5
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "config description at :pleroma-:mrf_emoji"
|
||||||
|
msgid "Reject or force-unlisted emojis whose URLs or names match a keyword or [Regex](https://hexdocs.pm/elixir/Regex.html)."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/pleroma/docs/translator.ex:5
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "config description at :pleroma-:mrf_emoji > :federated_timeline_removal_shortcode"
|
||||||
|
msgid " A list of patterns which result in message with emojis whose shortcodes match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses.\n\n Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.\n"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/pleroma/docs/translator.ex:5
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "config description at :pleroma-:mrf_emoji > :federated_timeline_removal_url"
|
||||||
|
msgid " A list of patterns which result in message with emojis whose URLs match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses.\n\n Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.\n"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/pleroma/docs/translator.ex:5
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "config description at :pleroma-:mrf_emoji > :remove_shortcode"
|
||||||
|
msgid " A list of patterns which result in emoji whose shortcode matches being removed from the message. This will apply to statuses, emoji reactions, and user profiles.\n\n Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.\n"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/pleroma/docs/translator.ex:5
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "config description at :pleroma-:mrf_emoji > :remove_url"
|
||||||
|
msgid " A list of patterns which result in emoji whose URL matches being removed from the message. This will apply to statuses, emoji reactions, and user profiles.\n\n Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.\n"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/pleroma/docs/translator.ex:5
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "config description at :pleroma-Pleroma.User.Backup > :process_chunk_size"
|
||||||
|
msgid "The number of activities to fetch in the backup job for each chunk."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/pleroma/docs/translator.ex:5
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "config description at :pleroma-Pleroma.User.Backup > :process_wait_time"
|
||||||
|
msgid "The amount of time to wait for backup to report progress, in milliseconds. If no progress is received from the backup job for that much time, terminate it and deem it failed."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/pleroma/docs/translator.ex:5
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "config label at :pleroma-:mrf_emoji"
|
||||||
|
msgid "MRF Emoji"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/pleroma/docs/translator.ex:5
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "config label at :pleroma-:mrf_emoji > :federated_timeline_removal_shortcode"
|
||||||
|
msgid "Federated timeline removal shortcode"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/pleroma/docs/translator.ex:5
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "config label at :pleroma-:mrf_emoji > :federated_timeline_removal_url"
|
||||||
|
msgid "Federated timeline removal url"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/pleroma/docs/translator.ex:5
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "config label at :pleroma-:mrf_emoji > :remove_shortcode"
|
||||||
|
msgid "Remove shortcode"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/pleroma/docs/translator.ex:5
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "config label at :pleroma-:mrf_emoji > :remove_url"
|
||||||
|
msgid "Remove url"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/pleroma/docs/translator.ex:5
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "config label at :pleroma-Pleroma.User.Backup > :process_chunk_size"
|
||||||
|
msgid "Process Chunk Size"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/pleroma/docs/translator.ex:5
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "config label at :pleroma-Pleroma.User.Backup > :process_wait_time"
|
||||||
|
msgid "Process Wait Time"
|
||||||
|
msgstr ""
|
||||||
|
|
|
@ -110,7 +110,7 @@ msgstr ""
|
||||||
msgid "Can't display this activity"
|
msgid "Can't display this activity"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:334
|
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:346
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Can't find user"
|
msgid "Can't find user"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -198,7 +198,7 @@ msgstr ""
|
||||||
msgid "Invalid password."
|
msgid "Invalid password."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:267
|
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:279
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Invalid request"
|
msgid "Invalid request"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -225,7 +225,7 @@ msgstr ""
|
||||||
#: lib/pleroma/web/feed/tag_controller.ex:16
|
#: lib/pleroma/web/feed/tag_controller.ex:16
|
||||||
#: lib/pleroma/web/feed/user_controller.ex:69
|
#: lib/pleroma/web/feed/user_controller.ex:69
|
||||||
#: lib/pleroma/web/o_status/o_status_controller.ex:132
|
#: lib/pleroma/web/o_status/o_status_controller.ex:132
|
||||||
#: lib/pleroma/web/plugs/uploaded_media.ex:104
|
#: lib/pleroma/web/plugs/uploaded_media.ex:84
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Not found"
|
msgid "Not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -235,7 +235,7 @@ msgstr ""
|
||||||
msgid "Poll's author can't vote"
|
msgid "Poll's author can't vote"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:499
|
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:511
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
|
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:39
|
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:39
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:51
|
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:51
|
||||||
|
@ -341,7 +341,7 @@ msgstr ""
|
||||||
msgid "CAPTCHA expired"
|
msgid "CAPTCHA expired"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/pleroma/web/plugs/uploaded_media.ex:77
|
#: lib/pleroma/web/plugs/uploaded_media.ex:57
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -361,7 +361,7 @@ msgstr ""
|
||||||
msgid "Insufficient permissions: %{permissions}."
|
msgid "Insufficient permissions: %{permissions}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/pleroma/web/plugs/uploaded_media.ex:131
|
#: lib/pleroma/web/plugs/uploaded_media.ex:111
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Internal Error"
|
msgid "Internal Error"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -557,7 +557,7 @@ msgstr ""
|
||||||
msgid "Access denied"
|
msgid "Access denied"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:331
|
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:343
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "This API requires an authenticated user"
|
msgid "This API requires an authenticated user"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -567,7 +567,7 @@ msgstr ""
|
||||||
msgid "User is not an admin."
|
msgid "User is not an admin."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/pleroma/user/backup.ex:73
|
#: lib/pleroma/user/backup.ex:78
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Last export was less than a day ago"
|
msgid "Last export was less than a day ago"
|
||||||
msgid_plural "Last export was less than %{days} days ago"
|
msgid_plural "Last export was less than %{days} days ago"
|
||||||
|
@ -607,3 +607,23 @@ msgstr ""
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "User isn't privileged."
|
msgid "User isn't privileged."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:267
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Bio is too long"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:270
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Name is too long"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:273
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "One or more field entries are too long"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:276
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Too many field entries"
|
||||||
|
msgstr ""
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue