Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
169 KB
Referenced Files
None
Subscribers
None
diff --git a/config/config.exs b/config/config.exs
index 7e374b22b..01dd09a18 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -1,973 +1,974 @@
# .i;;;;i.
# iYcviii;vXY:
# .YXi .i1c.
# .YC. . in7.
# .vc. ...... ;1c.
# i7, .. .;1;
# i7, .. ... .Y1i
# ,7v .6MMM@; .YX,
# .7;. ..IMMMMMM1 :t7.
# .;Y. ;$MMMMMM9. :tc.
# vY. .. .nMMM@MMU. ;1v.
# i7i ... .#MM@M@C. .....:71i
# it: .... $MMM@9;.,i;;;i,;tti
# :t7. ..... 0MMMWv.,iii:::,,;St.
# .nC. ..... IMMMQ..,::::::,.,czX.
# .ct: ....... .ZMMMI..,:::::::,,:76Y.
# c2: ......,i..Y$M@t..:::::::,,..inZY
# vov ......:ii..c$MBc..,,,,,,,,,,..iI9i
# i9Y ......iii:..7@MA,..,,,,,,,,,....;AA:
# iIS. ......:ii::..;@MI....,............;Ez.
# .I9. ......:i::::...8M1..................C0z.
# .z9; ......:i::::,.. .i:...................zWX.
# vbv ......,i::::,,. ................. :AQY
# c6Y. .,...,::::,,..:t0@@QY. ................ :8bi
# :6S. ..,,...,:::,,,..EMMMMMMI. ............... .;bZ,
# :6o, .,,,,..:::,,,..i#MMMMMM#v................. YW2.
# .n8i ..,,,,,,,::,,,,.. tMMMMM@C:.................. .1Wn
# 7Uc. .:::,,,,,::,,,,.. i1t;,..................... .UEi
# 7C...::::::::::::,,,,.. .................... vSi.
# ;1;...,,::::::,......... .................. Yz:
# v97,......... .voC.
# izAotX7777777777777777777777777777777777777777Y7n92:
# .;CoIIIIIUAA666666699999ZZZZZZZZZZZZZZZZZZZZ6ov.
#
# !!! ATTENTION !!!
# DO NOT EDIT THIS FILE! THIS FILE CONTAINS THE DEFAULT VALUES FOR THE CON-
# FIGURATION! EDIT YOUR SECRET FILE (either prod.secret.exs, dev.secret.exs).
#
# This file is responsible for configuring your application
# and its dependencies with the aid of the Config module.
#
# This configuration file is loaded before any dependency and
# is restricted to this project.
import Config
# General application configuration
config :pleroma, ecto_repos: [Pleroma.Repo]
config :pleroma, Pleroma.Repo,
telemetry_event: [Pleroma.Repo.Instrumenter],
migration_lock: :pg_advisory_lock
config :pleroma, Pleroma.Captcha,
enabled: true,
seconds_valid: 300,
method: Pleroma.Captcha.Native
config :pleroma, Pleroma.Captcha.Kocaptcha, endpoint: "https://captcha.kotobank.ch"
# Upload configuration
config :pleroma, Pleroma.Upload,
uploader: Pleroma.Uploaders.Local,
filters: [Pleroma.Upload.Filter.Dedupe],
link_name: false,
proxy_remote: false,
filename_display_max_length: 30,
default_description: nil,
base_url: nil,
allowed_mime_types: ["image", "audio", "video"]
config :pleroma, Pleroma.Uploaders.Local, uploads: "uploads"
config :pleroma, Pleroma.Uploaders.S3,
bucket: nil,
bucket_namespace: nil,
truncated_namespace: nil,
streaming_enabled: true
config :ex_aws, :s3,
# host: "s3.wasabisys.com", # required if not Amazon AWS
access_key_id: nil,
secret_access_key: nil,
# region: "us-east-1", # may be required for Amazon AWS
scheme: "https://"
config :pleroma, Pleroma.Uploaders.IPFS,
post_gateway_url: "http://localhost:5001",
get_gateway_url: "http://localhost:8080"
config :pleroma, :emoji,
shortcode_globs: ["/emoji/custom/**/*.png"],
pack_extensions: [".png", ".gif"],
groups: [
Custom: ["/emoji/*.png", "/emoji/**/*.png"]
],
default_manifest: "https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json",
shared_pack_cache_seconds_per_file: 60
config :pleroma, :uri_schemes,
valid_schemes: [
"https",
"http",
"dat",
"dweb",
"gopher",
"hyper",
"ipfs",
"ipns",
"irc",
"ircs",
"magnet",
"mailto",
"mumble",
"ssb",
"xmpp"
]
# Configures the endpoint
config :pleroma, Pleroma.Web.Endpoint,
url: [host: "localhost"],
http: [
ip: {127, 0, 0, 1}
],
protocol: "https",
secret_key_base: "aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl",
live_view: [signing_salt: "U5ELgdEwTD3n1+D5s0rY0AMg8/y1STxZ3Zvsl3bWh+oBcGrYdil0rXqPMRd3Glcq"],
signing_salt: "CqaoopA2",
render_errors: [view: Pleroma.Web.ErrorView, accepts: ~w(json)],
pubsub_server: Pleroma.PubSub,
secure_cookie_flag: true,
extra_cookie_attrs: [
"SameSite=Lax"
]
# Configures Elixir's Logger
config :logger, backends: [:console]
config :logger, :console,
level: :debug,
format: "\n$time $metadata[$level] $message\n",
metadata: [:actor, :path, :type, :user]
config :logger, :ex_syslogger,
level: :debug,
ident: "pleroma",
format: "$metadata[$level] $message",
metadata: [:actor, :path, :type, :user]
config :mime, :types, %{
"application/xml" => ["xml"],
"application/xrd+xml" => ["xrd+xml"],
"application/jrd+json" => ["jrd+json"],
"application/activity+json" => ["activity+json"],
"application/ld+json" => ["activity+json"],
# Can be removed when bumping MIME past 2.0.5
# see https://akkoma.dev/AkkomaGang/akkoma/issues/657
"image/apng" => ["apng"]
}
config :tesla, adapter: Tesla.Adapter.Hackney
# Configures http settings, upstream proxy etc.
config :pleroma, :http,
proxy_url: nil,
send_user_agent: true,
user_agent: :default,
adapter: []
config :pleroma, :instance,
name: "Pleroma",
email: "example@example.com",
notify_email: "noreply@example.com",
description: "Pleroma: An efficient and flexible fediverse server",
short_description: "",
background_image: "/images/city.jpg",
instance_thumbnail: "/instance/thumbnail.jpeg",
favicon: "/favicon.png",
limit: 5_000,
description_limit: 5_000,
remote_limit: 100_000,
upload_limit: 16_000_000,
avatar_upload_limit: 2_000_000,
background_upload_limit: 4_000_000,
banner_upload_limit: 4_000_000,
poll_limits: %{
max_options: 20,
max_option_chars: 200,
min_expiration: 0,
max_expiration: 365 * 24 * 60 * 60
},
registrations_open: true,
invites_enabled: false,
account_activation_required: false,
account_approval_required: false,
federating: true,
federation_incoming_replies_max_depth: 100,
allow_relay: true,
public: true,
quarantined_instances: [],
rejected_instances: [],
static_dir: "instance/static/",
allowed_post_formats: [
"text/plain",
"text/html",
"text/markdown",
"text/bbcode"
],
autofollowed_nicknames: [],
autofollowing_nicknames: [],
max_pinned_statuses: 1,
attachment_links: false,
max_report_comment_size: 1000,
report_strip_status: true,
safe_dm_mentions: false,
healthcheck: false,
remote_post_retention_days: 90,
skip_thread_containment: true,
limit_to_local_content: :unauthenticated,
user_bio_length: 5000,
user_name_length: 100,
max_account_fields: 10,
max_remote_account_fields: 20,
account_field_name_length: 512,
account_field_value_length: 2048,
registration_reason_length: 500,
external_user_synchronization: true,
extended_nickname_format: true,
cleanup_attachments: false,
multi_factor_authentication: [
totp: [
# digits 6 or 8
digits: 6,
period: 30
],
backup_codes: [
number: 5,
length: 16
]
],
show_reactions: true,
password_reset_token_validity: 60 * 60 * 24,
profile_directory: true,
admin_privileges: [
:users_read,
:users_manage_invites,
:users_manage_activation_state,
:users_manage_tags,
:users_manage_credentials,
:users_delete,
:messages_read,
:messages_delete,
:instances_delete,
:reports_manage_reports,
:moderation_log_read,
:announcements_manage_announcements,
:emoji_manage_emoji,
:statistics_read
],
moderator_privileges: [:messages_delete, :reports_manage_reports],
max_endorsed_users: 20,
birthday_required: false,
birthday_min_age: 0,
max_media_attachments: 1_000
config :pleroma, :welcome,
direct_message: [
enabled: false,
sender_nickname: nil,
message: nil
],
chat_message: [
enabled: false,
sender_nickname: nil,
message: nil
],
email: [
enabled: false,
sender: nil,
subject: "Welcome to <%= instance_name %>",
html: "Welcome to <%= instance_name %>",
text: "Welcome to <%= instance_name %>"
]
config :pleroma, :feed,
post_title: %{
max_length: 100,
omission: "..."
}
config :pleroma, :markup,
# XXX - unfortunately, inline images must be enabled by default right now, because
# of custom emoji. Issue #275 discusses defanging that somehow.
allow_inline_images: true,
allow_headings: false,
allow_tables: false,
allow_fonts: false,
scrub_policy: [
Pleroma.HTML.Scrubber.Default,
Pleroma.HTML.Transform.MediaProxy
]
config :pleroma, :frontend_configurations,
pleroma_fe: %{
alwaysShowSubjectInput: true,
background: "/images/city.jpg",
collapseMessageWithSubject: false,
disableChat: false,
greentext: false,
embeddedToS: true,
hideFilteredStatuses: false,
hideMutedPosts: false,
hidePostStats: false,
hideSitename: false,
hideUserStats: false,
loginMethod: "password",
logo: "/static/logo.svg",
logoMargin: ".1em",
logoMask: true,
minimalScopesMode: false,
noAttachmentLinks: false,
nsfwCensorImage: "",
postContentType: "text/plain",
redirectRootLogin: "/main/friends",
redirectRootNoLogin: "/main/all",
scopeCopy: true,
sidebarRight: false,
showFeaturesPanel: true,
showInstanceSpecificPanel: false,
subjectLineBehavior: "email",
theme: "pleroma-dark",
webPushNotifications: false
}
config :pleroma, :assets,
mascots: [
pleroma_fox_tan: %{
url: "/images/pleroma-fox-tan-smol.png",
mime_type: "image/png"
},
pleroma_fox_tan_shy: %{
url: "/images/pleroma-fox-tan-shy.png",
mime_type: "image/png"
}
],
default_mascot: :pleroma_fox_tan
config :pleroma, :manifest,
icons: [
%{
src: "/static/logo.svg",
sizes: "512x512",
purpose: "any",
type: "image/svg+xml"
}
],
theme_color: "#282c37",
background_color: "#191b22"
config :pleroma, :activitypub,
unfollow_blocked: true,
outgoing_blocks: true,
blockers_visible: true,
follow_handshake_timeout: 500,
note_replies_output_limit: 5,
sign_object_fetches: true,
authorized_fetch_mode: false,
client_api_enabled: false,
anonymize_reporter: false,
anonymize_reporter_local_nickname: ""
config :pleroma, :streamer,
workers: 3,
overflow_workers: 2
config :pleroma, :user, deny_follow_blocked: true
config :pleroma, :mrf_normalize_markup, scrub_policy: Pleroma.HTML.Scrubber.Default
config :pleroma, :mrf_rejectnonpublic,
allow_followersonly: false,
allow_direct: false
config :pleroma, :mrf_hellthread,
delist_threshold: 10,
reject_threshold: 20
config :pleroma, :mrf_simple,
media_removal: [],
media_nsfw: [],
federated_timeline_removal: [],
report_removal: [],
reject: [],
followers_only: [],
accept: [],
avatar_removal: [],
banner_removal: [],
reject_deletes: []
config :pleroma, :mrf_keyword,
reject: [],
federated_timeline_removal: [],
replace: []
config :pleroma, :mrf_emoji,
remove_url: [],
remove_shortcode: [],
federated_timeline_removal_url: [],
federated_timeline_removal_shortcode: []
config :pleroma, :mrf_hashtag,
sensitive: ["nsfw"],
reject: [],
federated_timeline_removal: []
config :pleroma, :mrf_subchain, match_actor: %{}
config :pleroma, :mrf_activity_expiration, days: 365
config :pleroma, :mrf_vocabulary,
accept: [],
reject: []
# threshold of 7 days
config :pleroma, :mrf_object_age,
threshold: 604_800,
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_inline_quote, template: "<bdi>RT:</bdi> {url}"
config :pleroma, :mrf_remote_report,
reject_all: false,
reject_anonymous: true,
reject_empty_message: true
config :pleroma, :mrf_force_mention,
mention_parent: true,
mention_quoted: true
config :pleroma, :mrf_antimentionspam, user_age_limit: 30_000
config :pleroma, :rich_media,
enabled: true,
ignore_hosts: [],
ignore_tld: ["local", "localdomain", "lan"],
parsers: [
Pleroma.Web.RichMedia.Parsers.TwitterCard,
Pleroma.Web.RichMedia.Parsers.OEmbed
],
timeout: 5_000,
ttl_setters: [
Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl,
Pleroma.Web.RichMedia.Parser.TTL.Opengraph
],
max_body: 5_000_000
config :pleroma, :media_proxy,
enabled: false,
invalidation: [
enabled: false,
provider: Pleroma.Web.MediaProxy.Invalidation.Script
],
proxy_opts: [
redirect_on_failure: false,
max_body_length: 25 * 1_048_576,
# Note: max_read_duration defaults to Pleroma.ReverseProxy.max_read_duration_default/1
max_read_duration: 30_000,
http: [
follow_redirect: true,
pool: :media
]
],
whitelist: []
config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Http,
method: :purge,
headers: [],
options: []
config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Script,
script_path: nil,
url_format: nil
# Note: media preview proxy depends on media proxy to be enabled
config :pleroma, :media_preview_proxy,
enabled: false,
thumbnail_max_width: 600,
thumbnail_max_height: 600,
image_quality: 85,
min_content_length: 100 * 1024
config :pleroma, :shout,
enabled: true,
limit: 5_000
config :phoenix, :format_encoders, json: Jason, "activity+json": Jason
config :phoenix, :json_library, Jason
config :phoenix, :filter_parameters, ["password", "confirm"]
config :pleroma, :gopher,
enabled: false,
ip: {0, 0, 0, 0},
port: 9999
config :pleroma, Pleroma.Web.Metadata,
providers: [
Pleroma.Web.Metadata.Providers.OpenGraph,
Pleroma.Web.Metadata.Providers.TwitterCard
],
unfurl_nsfw: false
config :pleroma, Pleroma.Web.Preload,
providers: [
Pleroma.Web.Preload.Providers.Instance
]
config :pleroma, :http_security,
enabled: true,
sts: false,
sts_max_age: 31_536_000,
ct_max_age: 2_592_000,
referrer_policy: "same-origin",
allow_unsafe_eval: false
config :cors_plug,
max_age: 86_400,
methods: ["POST", "PUT", "DELETE", "GET", "PATCH", "OPTIONS"],
expose: [
"Link",
"X-RateLimit-Reset",
"X-RateLimit-Limit",
"X-RateLimit-Remaining",
"X-Request-Id",
"Idempotency-Key"
],
credentials: true,
headers: ["Authorization", "Content-Type", "Idempotency-Key"]
config :pleroma, Pleroma.User,
restricted_nicknames: [
".well-known",
"~",
"about",
"activities",
"api",
"auth",
"check_password",
"dev",
"friend-requests",
"inbox",
"internal",
"main",
"media",
"nodeinfo",
"notice",
"oauth",
"objects",
"ostatus_subscribe",
"pleroma",
"proxy",
"push",
"registration",
"relay",
"settings",
"status",
"tag",
"user-search",
"user_exists",
"users",
"web",
"verify_credentials",
"update_credentials",
"relationships",
"search",
"confirmation_resend",
"mfa"
],
email_blacklist: []
# The Pruner :max_age must be longer than Worker :unique
# value or it cannot enforce uniqueness.
config :pleroma, Oban,
repo: Pleroma.Repo,
notifier: Oban.Notifiers.PG,
log: false,
queues: [
activity_expiration: 10,
federator_incoming: 5,
federator_outgoing: 25,
web_push: 50,
background: 20,
search_indexing: [limit: 10, paused: true],
slow: 5
],
plugins: [Oban.Plugins.Lazarus, {Oban.Plugins.Pruner, max_age: 900}],
crontab: [
{"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
{"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker},
{"*/10 * * * *", Pleroma.Workers.Cron.AppCleanupWorker}
]
config :pleroma, Pleroma.Formatter,
class: false,
rel: "ugc",
new_window: false,
truncate: false,
strip_prefix: false,
extra: true,
validate_tld: :no_scheme
config :pleroma, :ldap,
enabled: System.get_env("LDAP_ENABLED") == "true",
host: System.get_env("LDAP_HOST", "localhost"),
port: String.to_integer(System.get_env("LDAP_PORT", "389")),
ssl: System.get_env("LDAP_SSL") == "true",
sslopts: [],
tls: System.get_env("LDAP_TLS") == "true",
tlsopts: [],
base: System.get_env("LDAP_BASE", "dc=example,dc=com"),
uid: System.get_env("LDAP_UID", "cn"),
# defaults to CAStore's Mozilla roots
cacertfile: System.get_env("LDAP_CACERTFILE", nil),
mail: System.get_env("LDAP_MAIL", "mail")
oauth_consumer_strategies =
System.get_env("OAUTH_CONSUMER_STRATEGIES")
|> to_string()
|> String.split()
|> Enum.map(&hd(String.split(&1, ":")))
ueberauth_providers =
for strategy <- oauth_consumer_strategies do
strategy_module_name = "Elixir.Ueberauth.Strategy.#{String.capitalize(strategy)}"
strategy_module = String.to_atom(strategy_module_name)
{String.to_atom(strategy), {strategy_module, [callback_params: ["state"]]}}
end
config :ueberauth,
Ueberauth,
base_path: "/oauth",
providers: ueberauth_providers
config :pleroma, :auth, oauth_consumer_strategies: oauth_consumer_strategies
config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Sendmail, enabled: false
config :pleroma, Pleroma.Emails.UserEmail,
logo: nil,
styling: %{
link_color: "#d8a070",
background_color: "#2C3645",
content_background_color: "#1B2635",
header_color: "#d8a070",
text_color: "#b9b9ba",
text_muted_color: "#b9b9ba"
}
config :pleroma, Pleroma.Emails.NewUsersDigestEmail, enabled: false
config :pleroma, Pleroma.PromEx,
disabled: false,
manual_metrics_start_delay: :no_delay,
drop_metrics_groups: [],
grafana: [
host: System.get_env("GRAFANA_HOST", "http://localhost:3000"),
auth_token: System.get_env("GRAFANA_TOKEN"),
upload_dashboards_on_start: false,
folder_name: "BEAM",
annotate_app_lifecycle: true
],
metrics_server: [
port: 4021,
path: "/metrics",
protocol: :http,
pool_size: 5,
cowboy_opts: [],
auth_strategy: :none
],
datasource: "Prometheus"
config :pleroma, Pleroma.ScheduledActivity,
daily_user_limit: 25,
total_user_limit: 300,
enabled: true
config :pleroma, :email_notifications,
digest: %{
active: false,
interval: 7,
inactivity_threshold: 7
}
config :pleroma, :oauth2,
token_expires_in: 3600 * 24 * 365 * 100,
issue_new_refresh_token: true,
clean_expired_tokens: false
config :pleroma, :database, rum_enabled: false
config :pleroma, :features, improved_hashtag_timeline: :auto
config :pleroma, :populate_hashtags_table, fault_rate_allowance: 0.01
config :pleroma, :delete_context_objects, fault_rate_allowance: 0.01
config :pleroma, :env, Mix.env()
config :http_signatures,
adapter: Pleroma.Signature
config :pleroma, :rate_limit,
authentication: {60_000, 15},
timeline: {500, 3},
search: [{1000, 10}, {1000, 30}],
app_account_creation: {1_800_000, 25},
oauth_app_creation: {900_000, 5},
relations_actions: {10_000, 10},
relation_id_action: {60_000, 2},
statuses_actions: {10_000, 15},
status_id_action: {60_000, 3},
password_reset: {1_800_000, 5},
account_confirmation_resend: {8_640_000, 5},
ap_routes: {60_000, 15}
config :pleroma, Pleroma.Workers.PurgeExpiredActivity, enabled: true, min_lifetime: 600
config :pleroma, Pleroma.Web.Plugs.RemoteIp,
enabled: true,
headers: ["x-forwarded-for"],
proxies: [],
reserved: [
"127.0.0.0/8",
"::1/128",
"fc00::/7",
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16"
]
config :pleroma, :static_fe, enabled: false
# Example of frontend configuration
# This example will make us serve the primary frontend from the
# frontends directory within your `:pleroma, :instance, static_dir`.
# e.g., instance/static/frontends/pleroma/develop/
#
# With no frontend configuration, the bundled files from the `static` directory will
# be used.
#
# config :pleroma, :frontends,
# primary: %{"name" => "pleroma-fe", "ref" => "develop"},
# admin: %{"name" => "admin-fe", "ref" => "stable"},
# available: %{...}
config :pleroma, :frontends,
available: %{
"kenoma" => %{
"name" => "kenoma",
"git" => "https://git.pleroma.social/lambadalambda/kenoma",
"build_url" =>
"https://git.pleroma.social/lambadalambda/kenoma/-/jobs/artifacts/${ref}/download?job=build",
"ref" => "master"
},
"pleroma-fe" => %{
"name" => "pleroma-fe",
"git" => "https://git.pleroma.social/pleroma/pleroma-fe",
"build_url" =>
"https://git.pleroma.social/pleroma/pleroma-fe/-/jobs/artifacts/${ref}/download?job=build",
"ref" => "develop"
},
"fedi-fe" => %{
"name" => "fedi-fe",
"git" => "https://git.pleroma.social/pleroma/fedi-fe",
"build_url" =>
"https://git.pleroma.social/pleroma/fedi-fe/-/jobs/artifacts/${ref}/download?job=build_release",
"ref" => "master",
"custom-http-headers" => [
{"service-worker-allowed", "/"}
]
},
"admin-fe" => %{
"name" => "admin-fe",
"git" => "https://git.pleroma.social/pleroma/admin-fe",
"build_url" =>
"https://git.pleroma.social/pleroma/admin-fe/-/jobs/artifacts/${ref}/download?job=build",
"ref" => "develop"
},
"soapbox" => %{
"name" => "soapbox",
"git" => "https://gitlab.com/soapbox-pub/soapbox",
"build_url" =>
"https://gitlab.com/soapbox-pub/soapbox/-/jobs/artifacts/${ref}/download?job=build-production",
"ref" => "v3.0.0-beta.1",
"build_dir" => "static"
},
"glitch-lily" => %{
"name" => "glitch-lily",
"git" => "https://lily-is.land/infra/glitch-lily",
"build_url" =>
"https://lily-is.land/infra/glitch-lily/-/jobs/artifacts/${ref}/download?job=build",
"ref" => "servant",
"build_dir" => "public"
},
"pl-fe" => %{
"name" => "pl-fe",
"git" => "https://github.com/mkljczk/pl-fe",
"build_url" => "https://pl.mkljczk.pl/pl-fe.zip",
"ref" => "develop",
"build_dir" => "."
}
}
config :pleroma, :web_cache_ttl,
activity_pub: nil,
activity_pub_question: 30_000
config :pleroma, :modules, runtime_dir: "instance/modules"
config :pleroma, configurable_from_database: false
config :pleroma, Pleroma.Repo,
parameters: [gin_fuzzy_search_limit: "500", jit: "off"],
prepare: :unnamed
config :pleroma, :connections_pool,
reclaim_multiplier: 0.1,
connection_acquisition_wait: 250,
connection_acquisition_retries: 5,
max_connections: 250,
max_idle_time: 30_000,
retry: 0,
connect_timeout: 5_000
config :pleroma, :pools,
federation: [
size: 75,
max_waiting: 20,
recv_timeout: 10_000
],
media: [
size: 75,
max_waiting: 20,
recv_timeout: 15_000
],
rich_media: [
size: 25,
max_waiting: 20,
recv_timeout: 15_000
],
upload: [
size: 25,
max_waiting: 20,
recv_timeout: 15_000
],
default: [
size: 50,
max_waiting: 2,
recv_timeout: 5_000
]
config :pleroma, :hackney_pools,
federation: [
max_connections: 50,
timeout: 10_000
],
media: [
max_connections: 50,
timeout: 15_000
],
rich_media: [
max_connections: 50,
timeout: 15_000
],
upload: [
max_connections: 25,
timeout: 15_000
]
config :pleroma, :majic_pool, size: 2
private_instance? = :if_instance_is_private
config :pleroma, :restrict_unauthenticated,
timelines: %{local: private_instance?, federated: private_instance?},
profiles: %{local: private_instance?, remote: private_instance?},
activities: %{local: private_instance?, remote: private_instance?}
config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: false
config :pleroma, :mrf,
policies: [
Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy,
Pleroma.Web.ActivityPub.MRF.TagPolicy,
Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy
],
transparency: true,
transparency_exclusions: []
config :tzdata, :http_client, Pleroma.HTTP.Tzdata
config :ex_aws, http_client: Pleroma.HTTP.ExAws
config :web_push_encryption, http_client: Pleroma.HTTP.WebPush
config :pleroma, :instances_favicons, enabled: false
config :floki, :html_parser, Floki.HTMLParser.FastHtml
config :pleroma, Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.PleromaAuthenticator
config :pleroma, Pleroma.User.Backup,
purge_after_days: 30,
limit_days: 7,
dir: nil,
process_chunk_size: 100,
timeout: :timer.minutes(30)
config :pleroma, ConcurrentLimiter, [
{Pleroma.Search, [max_running: 30, max_waiting: 50]}
]
config :pleroma, Pleroma.Web.WebFinger, domain: nil, update_nickname_on_user_fetch: true
config :pleroma, Pleroma.Search, module: Pleroma.Search.DatabaseSearch
config :pleroma, Pleroma.Search.Meilisearch,
url: "http://127.0.0.1:7700/",
private_key: nil,
initial_indexing_chunk_size: 100_000
config :pleroma, Pleroma.Search.ParadeDB,
url: nil,
- table: "pleroma_search_documents"
+ table: "pleroma_search_documents",
+ fuzzy_distance: 0
config :pleroma, Pleroma.Search.ParadeDB.Repo,
pool_size: 10,
prepare: :unnamed
config :pleroma, Pleroma.Application,
background_migrators: true,
internal_fetch: true,
load_custom_modules: true,
max_restarts: 3,
streamer_registry: true
config :pleroma, Pleroma.Uploaders.Uploader, timeout: 30_000
config :pleroma, Pleroma.Search.QdrantSearch,
qdrant_url: "http://127.0.0.1:6333/",
qdrant_api_key: "",
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_api_key: "",
qdrant_index_configuration: %{
vectors: %{size: 384, distance: "Cosine"}
}
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"
diff --git a/config/description.exs b/config/description.exs
index de454dfb2..9a00afdcd 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -1,3648 +1,3654 @@
import Config
websocket_config = [
path: "/websocket",
serializer: [
{Phoenix.Socket.V1.JSONSerializer, "~> 1.0.0"},
{Phoenix.Socket.V2.JSONSerializer, "~> 2.0.0"}
],
timeout: 60_000,
transport_log: false,
compress: false
]
installed_frontend_options = [
%{
key: "name",
label: "Name",
type: :string,
description:
"Name of the installed frontend. Valid config must include both `Name` and `Reference` values."
},
%{
key: "ref",
label: "Reference",
type: :string,
description:
"Reference of the installed frontend to be used. Valid config must include both `Name` and `Reference` values."
}
]
frontend_options = [
%{
key: "name",
label: "Name",
type: :string,
description: "Name of the frontend."
},
%{
key: "ref",
label: "Reference",
type: :string,
description: "Reference of the frontend to be used."
},
%{
key: "git",
label: "Git Repository URL",
type: :string,
description: "URL of the git repository of the frontend"
},
%{
key: "build_url",
label: "Build URL",
type: :string,
description:
"Either an url to a zip file containing the frontend or a template to build it by inserting the `ref`. The string `${ref}` will be replaced by the configured `ref`.",
example: "https://some.url/builds/${ref}.zip"
},
%{
key: "build_dir",
label: "Build directory",
type: :string,
description: "The directory inside the zip file "
},
%{
key: "custom-http-headers",
label: "Custom HTTP headers",
type: {:list, :string},
description: "The custom HTTP headers for the frontend"
}
]
config :pleroma, :config_description, [
%{
group: :pleroma,
key: Pleroma.Upload,
type: :group,
description: "Upload general settings",
children: [
%{
key: :uploader,
type: :module,
description: "Module which will be used for uploads",
suggestions: {:list_behaviour_implementations, Pleroma.Uploaders.Uploader}
},
%{
key: :filters,
type: {:list, :module},
description:
"List of filter modules for uploads. Module names are shortened (removed leading `Pleroma.Upload.Filter.` part), but on adding custom module you need to use full name.",
suggestions: {:list_behaviour_implementations, Pleroma.Upload.Filter}
},
%{
key: :link_name,
type: :boolean,
description:
"If enabled, a name parameter will be added to the URL of the upload. For example `https://instance.tld/media/imagehash.png?name=realname.png`."
},
%{
key: :base_url,
label: "Base URL",
type: :string,
description:
"Base URL for the uploads. Required if you use a CDN or host attachments under a different domain.",
suggestions: [
"https://cdn-host.com"
]
},
%{
key: :proxy_remote,
type: :boolean,
description: """
Proxy requests to the remote uploader.\n
Useful if media upload endpoint is not internet accessible.
"""
},
%{
key: :filename_display_max_length,
type: :integer,
description: "Set max length of a filename to display. 0 = no limit. Default: 30"
},
%{
key: :allowed_mime_types,
label: "Allowed MIME types",
type: {:list, :string},
description:
"List of MIME (main) types uploads are allowed to identify themselves with. Other types may still be uploaded, but will identify as a generic binary to clients. WARNING: Loosening this over the defaults can lead to security issues. Removing types is safe, but only add to the list if you are sure you know what you are doing.",
suggestions: [
"image",
"audio",
"video",
"font"
]
}
]
},
%{
group: :pleroma,
key: Pleroma.Uploaders.Local,
type: :group,
description: "Local uploader-related settings",
children: [
%{
key: :uploads,
type: :string,
description: "Path where user's uploads will be saved",
suggestions: [
"uploads"
]
}
]
},
%{
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,
key: Pleroma.Uploaders.S3,
type: :group,
description: "S3 uploader-related settings",
children: [
%{
key: :bucket,
type: :string,
description: "S3 bucket",
suggestions: [
"bucket"
]
},
%{
key: :bucket_namespace,
type: :string,
description: "S3 bucket namespace",
suggestions: ["pleroma"]
},
%{
key: :truncated_namespace,
type: :string,
description:
"If you use S3 compatible service such as Digital Ocean Spaces or CDN, set folder name or \"\" etc." <>
" For example, when using CDN to S3 virtual host format, set \"\". At this time, write CNAME to CDN in Upload base_url."
},
%{
key: :streaming_enabled,
type: :boolean,
description:
"Enable streaming uploads, when enabled the file will be sent to the server in chunks as it's being read. This may be unsupported by some providers, try disabling this if you have upload problems."
}
]
},
%{
group: :pleroma,
key: Pleroma.Upload.Filter.Mogrify,
type: :group,
description: "Uploads mogrify filter settings",
children: [
%{
key: :args,
type: [:string, {:list, :string}, {:list, :tuple}],
description:
"List of actions for the mogrify command. It's possible to add self-written settings as string. " <>
"For example `auto-orient, strip, {\"resize\", \"3840x1080>\"}` value will be parsed into valid list of the settings.",
suggestions: [
"strip",
"auto-orient",
{"implode", "1"}
]
}
]
},
%{
group: :pleroma,
key: Pleroma.Upload.Filter.AnonymizeFilename,
type: :group,
description: "Filter replaces the filename of the upload",
children: [
%{
key: :text,
type: :string,
description:
"Text to replace filenames in links. If no setting, {random}.extension will be used. You can get the original" <>
" filename extension by using {extension}, for example custom-file-name.{extension}.",
suggestions: [
"custom-file-name.{extension}"
]
}
]
},
%{
group: :pleroma,
key: Pleroma.Emails.Mailer,
type: :group,
description: "Mailer-related settings",
children: [
%{
key: :enabled,
label: "Mailer Enabled",
type: :boolean
},
%{
key: :adapter,
type: :module,
description:
"One of the mail adapters listed in [Swoosh documentation](https://hexdocs.pm/swoosh/Swoosh.html#module-adapters)",
suggestions: [
Swoosh.Adapters.AmazonSES,
Swoosh.Adapters.Dyn,
Swoosh.Adapters.Gmail,
Swoosh.Adapters.Mailgun,
Swoosh.Adapters.Mailjet,
Swoosh.Adapters.Mandrill,
Swoosh.Adapters.Postmark,
Swoosh.Adapters.SMTP,
Swoosh.Adapters.Sendgrid,
Swoosh.Adapters.Sendmail,
Swoosh.Adapters.SocketLabs,
Swoosh.Adapters.SparkPost
]
},
%{
group: {:subgroup, Swoosh.Adapters.SMTP},
key: :relay,
type: :string,
description: "Hostname or IP address",
suggestions: ["smtp.example.com"]
},
%{
group: {:subgroup, Swoosh.Adapters.SMTP},
key: :port,
type: :integer,
description: "SMTP port",
suggestions: ["1025"]
},
%{
group: {:subgroup, Swoosh.Adapters.SMTP},
key: :username,
type: :string,
description: "SMTP AUTH username",
suggestions: ["user@example.com"]
},
%{
group: {:subgroup, Swoosh.Adapters.SMTP},
key: :password,
type: :string,
description: "SMTP AUTH password",
suggestions: ["password"]
},
%{
group: {:subgroup, Swoosh.Adapters.SMTP},
key: :ssl,
label: "Use SSL",
type: :boolean,
description: "Use Implicit SSL/TLS. e.g. port 465"
},
%{
group: {:subgroup, Swoosh.Adapters.SMTP},
key: :tls,
label: "STARTTLS Mode",
type: {:dropdown, :atom},
description: "Explicit TLS (STARTTLS) enforcement mode",
suggestions: [:if_available, :always, :never]
},
%{
group: {:subgroup, Swoosh.Adapters.SMTP},
key: :auth,
label: "AUTH Mode",
type: {:dropdown, :atom},
description: "SMTP AUTH enforcement mode",
suggestions: [:if_available, :always, :never]
},
%{
group: {:subgroup, Swoosh.Adapters.SMTP},
key: :retries,
type: :integer,
description: "SMTP temporary (4xx) error retries",
suggestions: [1]
},
%{
group: {:subgroup, Swoosh.Adapters.Sendgrid},
key: :api_key,
label: "SendGrid API Key",
type: :string,
suggestions: ["YOUR_API_KEY"]
},
%{
group: {:subgroup, Swoosh.Adapters.Sendmail},
key: :cmd_path,
type: :string,
suggestions: ["/usr/bin/sendmail"]
},
%{
group: {:subgroup, Swoosh.Adapters.Sendmail},
key: :cmd_args,
type: :string,
suggestions: ["-N delay,failure,success"]
},
%{
group: {:subgroup, Swoosh.Adapters.Sendmail},
key: :qmail,
label: "Qmail compat mode",
type: :boolean
},
%{
group: {:subgroup, Swoosh.Adapters.Mandrill},
key: :api_key,
label: "Mandrill API Key",
type: :string,
suggestions: ["YOUR_API_KEY"]
},
%{
group: {:subgroup, Swoosh.Adapters.Mailgun},
key: :api_key,
label: "Mailgun API Key",
type: :string,
suggestions: ["YOUR_API_KEY"]
},
%{
group: {:subgroup, Swoosh.Adapters.Mailgun},
key: :domain,
type: :string,
suggestions: ["YOUR_DOMAIN_NAME"]
},
%{
group: {:subgroup, Swoosh.Adapters.Mailjet},
key: :api_key,
label: "MailJet Public API Key",
type: :string,
suggestions: ["MJ_APIKEY_PUBLIC"]
},
%{
group: {:subgroup, Swoosh.Adapters.Mailjet},
key: :secret,
label: "MailJet Private API Key",
type: :string,
suggestions: ["MJ_APIKEY_PRIVATE"]
},
%{
group: {:subgroup, Swoosh.Adapters.Postmark},
key: :api_key,
label: "Postmark API Key",
type: :string,
suggestions: ["X-Postmark-Server-Token"]
},
%{
group: {:subgroup, Swoosh.Adapters.SparkPost},
key: :api_key,
label: "SparkPost API key",
type: :string,
suggestions: ["YOUR_API_KEY"]
},
%{
group: {:subgroup, Swoosh.Adapters.SparkPost},
key: :endpoint,
type: :string,
suggestions: ["https://api.sparkpost.com/api/v1"]
},
%{
group: {:subgroup, Swoosh.Adapters.AmazonSES},
key: :access_key,
label: "AWS Access Key",
type: :string,
suggestions: ["AWS_ACCESS_KEY"]
},
%{
group: {:subgroup, Swoosh.Adapters.AmazonSES},
key: :secret,
label: "AWS Secret Key",
type: :string,
suggestions: ["AWS_SECRET_KEY"]
},
%{
group: {:subgroup, Swoosh.Adapters.AmazonSES},
key: :region,
label: "AWS Region",
type: :string,
suggestions: ["us-east-1", "us-east-2"]
},
%{
group: {:subgroup, Swoosh.Adapters.Dyn},
key: :api_key,
label: "Dyn API Key",
type: :string,
suggestions: ["apikey"]
},
%{
group: {:subgroup, Swoosh.Adapters.SocketLabs},
key: :api_key,
label: "SocketLabs API Key",
type: :string,
suggestions: ["INJECTION_API_KEY"]
},
%{
group: {:subgroup, Swoosh.Adapters.SocketLabs},
key: :server_id,
label: "Server ID",
type: :string,
suggestions: ["SERVER_ID"]
},
%{
group: {:subgroup, Swoosh.Adapters.Gmail},
key: :access_token,
label: "GMail API Access Token",
type: :string,
suggestions: ["GMAIL_API_ACCESS_TOKEN"]
}
]
},
%{
group: :pleroma,
key: :uri_schemes,
label: "URI Schemes",
type: :group,
description: "URI schemes related settings",
children: [
%{
key: :valid_schemes,
type: {:list, :string},
description: "List of the scheme part that is considered valid to be an URL",
suggestions: [
"https",
"http",
"dat",
"dweb",
"gopher",
"hyper",
"ipfs",
"ipns",
"irc",
"ircs",
"magnet",
"mailto",
"mumble",
"ssb",
"xmpp"
]
}
]
},
%{
group: :pleroma,
key: :features,
type: :group,
description: "Customizable features",
children: [
%{
key: :improved_hashtag_timeline,
type: {:dropdown, :atom},
description:
"Setting to force toggle / force disable improved hashtags timeline. `:enabled` forces hashtags to be fetched from `hashtags` table for hashtags timeline. `:disabled` forces object-embedded hashtags to be used (slower). Keep it `:auto` for automatic behaviour (it is auto-set to `:enabled` [unless overridden] when HashtagsTableMigrator completes).",
suggestions: [:auto, :enabled, :disabled]
}
]
},
%{
group: :pleroma,
key: :populate_hashtags_table,
type: :group,
description: "`populate_hashtags_table` background migration settings",
children: [
%{
key: :fault_rate_allowance,
type: :float,
description:
"Max accepted rate of objects that failed in the migration. Any value from 0.0 which tolerates no errors to 1.0 which will enable the feature even if hashtags transfer failed for all records.",
suggestions: [0.01]
},
%{
key: :sleep_interval_ms,
type: :integer,
description:
"Sleep interval between each chunk of processed records in order to decrease the load on the system (defaults to 0 and should be keep default on most instances)."
}
]
},
%{
group: :pleroma,
key: :delete_context_objects,
type: :group,
description: "`delete_context_objects` background migration settings",
children: [
%{
key: :fault_rate_allowance,
type: :float,
description:
"Max accepted rate of objects that failed in the migration. Any value from 0.0 which tolerates no errors to 1.0 which will enable the feature even if context object deletion failed for all records.",
suggestions: [0.01]
},
%{
key: :sleep_interval_ms,
type: :integer,
description:
"Sleep interval between each chunk of processed records in order to decrease the load on the system (defaults to 0 and should be keep default on most instances)."
}
]
},
%{
group: :pleroma,
key: :instance,
type: :group,
description: "Instance-related settings",
children: [
%{
key: :name,
type: :string,
description: "Name of the instance",
suggestions: [
"Pleroma"
]
},
%{
key: :email,
label: "Admin Email Address",
type: :string,
description: "Email used to reach an Administrator/Moderator of the instance",
suggestions: [
"email@example.com"
]
},
%{
key: :notify_email,
label: "Sender Email Address",
type: :string,
description: "Envelope FROM address for mail sent via Pleroma",
suggestions: [
"notify@example.com"
]
},
%{
key: :description,
type: :string,
description:
"The instance's description. It can be seen in nodeinfo and `/api/v1/instance`",
suggestions: [
"Very cool instance"
]
},
%{
key: :short_description,
type: :string,
description:
"Shorter version of instance description. It can be seen on `/api/v1/instance`",
suggestions: [
"Cool instance"
]
},
%{
key: :status_page,
type: :string,
description: "A page where people can see the status of the server during an outage",
suggestions: [
"https://status.pleroma.example.org"
]
},
%{
key: :contact_username,
type: :string,
description: "Instance owner username",
suggestions: ["admin"]
},
%{
key: :limit,
type: :integer,
description: "Posts character limit (CW/Subject included in the counter)",
suggestions: [
5_000
]
},
%{
key: :remote_limit,
type: :integer,
description: "Hard character limit beyond which remote posts will be dropped",
suggestions: [
100_000
]
},
%{
key: :max_media_attachments,
type: :integer,
description: "Maximum number of post media attachments",
suggestions: [
1_000_000
]
},
%{
key: :upload_limit,
type: :integer,
description: "File size limit of uploads (except for avatar, background, banner)",
suggestions: [
16_000_000
]
},
%{
key: :avatar_upload_limit,
type: :integer,
description: "File size limit of user's profile avatars",
suggestions: [
2_000_000
]
},
%{
key: :background_upload_limit,
type: :integer,
description: "File size limit of user's profile backgrounds",
suggestions: [
4_000_000
]
},
%{
key: :banner_upload_limit,
type: :integer,
description: "File size limit of user's profile banners",
suggestions: [
4_000_000
]
},
%{
key: :poll_limits,
type: :map,
description: "A map with poll limits for local polls",
suggestions: [
%{
max_options: 20,
max_option_chars: 200,
min_expiration: 0,
max_expiration: 31_536_000
}
],
children: [
%{
key: :max_options,
type: :integer,
description: "Maximum number of options",
suggestions: [20]
},
%{
key: :max_option_chars,
type: :integer,
description: "Maximum number of characters per option",
suggestions: [200]
},
%{
key: :min_expiration,
type: :integer,
description: "Minimum expiration time (in seconds)",
suggestions: [0]
},
%{
key: :max_expiration,
type: :integer,
description: "Maximum expiration time (in seconds)",
suggestions: [3600]
}
]
},
%{
key: :registrations_open,
type: :boolean,
description:
"Enable registrations for anyone. Invitations require this setting to be disabled."
},
%{
key: :invites_enabled,
type: :boolean,
description:
"Enable user invitations for admins (depends on `registrations_open` being disabled)"
},
%{
key: :account_activation_required,
type: :boolean,
description: "Require users to confirm their emails before signing in"
},
%{
key: :account_approval_required,
type: :boolean,
description: "Require users to be manually approved by an admin before signing in"
},
%{
key: :federating,
type: :boolean,
description: "Enable federation with other instances"
},
%{
key: :federation_incoming_replies_max_depth,
label: "Fed. incoming replies max depth",
type: :integer,
description:
"Max. depth of reply-to and reply activities fetching on incoming federation, to prevent out-of-memory situations while" <>
" fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes.",
suggestions: [
100
]
},
%{
key: :federation_reachability_timeout_days,
label: "Fed. reachability timeout days",
type: :integer,
description:
"Timeout (in days) of each external federation target being unreachable prior to pausing federating to it",
suggestions: [
7
]
},
%{
key: :allow_relay,
type: :boolean,
description:
"Permits remote instances to subscribe to all public posts of your instance. (Important!) This may increase the visibility of your instance."
},
%{
key: :public,
type: :boolean,
description:
"Makes the client API in authenticated mode-only except for user-profiles." <>
" Useful for disabling the Local Timeline and The Whole Known Network. " <>
" Note: when setting to `false`, please also check `:restrict_unauthenticated` setting."
},
%{
key: :quarantined_instances,
type: {:list, :tuple},
key_placeholder: "instance",
value_placeholder: "reason",
description:
"List of ActivityPub instances where private (DMs, followers-only) activities will not be sent and the reason for doing so",
suggestions: [
{"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,
type: :string,
description: "Instance static directory",
suggestions: [
"instance/static/"
]
},
%{
key: :allowed_post_formats,
type: {:list, :string},
description: "MIME-type list of formats allowed to be posted (transformed into HTML)",
suggestions: [
"text/plain",
"text/html",
"text/markdown",
"text/bbcode"
]
},
%{
key: :extended_nickname_format,
type: :boolean,
description:
"Enable to use extended local nicknames format (allows underscores/dashes)." <>
" This will break federation with older software for theses nicknames."
},
%{
key: :cleanup_attachments,
type: :boolean,
description: """
Enable to remove associated attachments when status is removed.
This will not affect duplicates and attachments without status.
Enabling this will increase load to database when deleting statuses on larger instances.
"""
},
%{
key: :max_pinned_statuses,
type: :integer,
description: "The maximum number of pinned statuses. 0 will disable the feature.",
suggestions: [
0,
1,
3
]
},
%{
key: :max_endorsed_users,
type: :integer,
description: "The maximum number of recommended accounts. 0 will disable the feature.",
suggestions: [
0,
1,
3
]
},
%{
key: :autofollowed_nicknames,
type: {:list, :string},
description:
"Set to nicknames of (local) users that every new user should automatically follow"
},
%{
key: :autofollowing_nicknames,
type: {:list, :string},
description:
"Set to nicknames of (local) users that automatically follows every newly registered user"
},
%{
key: :attachment_links,
type: :boolean,
description: "Enable to automatically add attachment link text to statuses"
},
%{
key: :max_report_comment_size,
type: :integer,
description: "The maximum size of the report comment. Default: 1000.",
suggestions: [
1_000
]
},
%{
key: :report_strip_status,
label: "Report strip status",
type: :boolean,
description:
"Strip associated statuses in reports to ids when closed/resolved, otherwise keep a copy"
},
%{
key: :safe_dm_mentions,
label: "Safe DM mentions",
type: :boolean,
description:
"If enabled, only mentions at the beginning of a post will be used to address people in direct messages." <>
" This is to prevent accidental mentioning of people when talking about them (e.g. \"@admin please keep an eye on @bad_actor\")." <>
" Default: disabled"
},
%{
key: :healthcheck,
type: :boolean,
description: "If enabled, system data will be shown on `/api/pleroma/healthcheck`"
},
%{
key: :remote_post_retention_days,
type: :integer,
description:
"The default amount of days to retain remote posts when pruning the database",
suggestions: [
90
]
},
%{
key: :user_bio_length,
type: :integer,
description: "A user bio maximum length. Default: 5000.",
suggestions: [
5_000
]
},
%{
key: :user_name_length,
type: :integer,
description: "A user name maximum length. Default: 100.",
suggestions: [
100
]
},
%{
key: :skip_thread_containment,
type: :boolean,
description: "Skip filtering out broken threads. Default: enabled."
},
%{
key: :limit_to_local_content,
type: {:dropdown, :atom},
description:
"Limit unauthenticated users to search for local statutes and users only. Default: `:unauthenticated`.",
suggestions: [
:unauthenticated,
:all,
false
]
},
%{
key: :max_account_fields,
type: :integer,
description: "The maximum number of custom fields in the user profile. Default: 10.",
suggestions: [
10
]
},
%{
key: :max_remote_account_fields,
type: :integer,
description:
"The maximum number of custom fields in the remote user profile. Default: 20.",
suggestions: [
20
]
},
%{
key: :account_field_name_length,
type: :integer,
description: "An account field name maximum length. Default: 512.",
suggestions: [
512
]
},
%{
key: :account_field_value_length,
type: :integer,
description: "An account field value maximum length. Default: 2048.",
suggestions: [
2048
]
},
%{
key: :registration_reason_length,
type: :integer,
description: "Maximum registration reason length. Default: 500.",
suggestions: [
500
]
},
%{
key: :external_user_synchronization,
type: :boolean,
description: "Enabling following/followers counters synchronization for external users"
},
%{
key: :multi_factor_authentication,
type: :keyword,
description: "Multi-factor authentication settings",
suggestions: [
[
totp: [digits: 6, period: 30],
backup_codes: [number: 5, length: 16]
]
],
children: [
%{
key: :totp,
label: "TOTP settings",
type: :keyword,
description: "TOTP settings",
suggestions: [digits: 6, period: 30],
children: [
%{
key: :digits,
type: :integer,
suggestions: [6],
description:
"Determines the length of a one-time pass-code, in characters. Defaults to 6 characters."
},
%{
key: :period,
type: :integer,
suggestions: [30],
description:
"A period for which the TOTP code will be valid, in seconds. Defaults to 30 seconds."
}
]
},
%{
key: :backup_codes,
type: :keyword,
description: "MFA backup codes settings",
suggestions: [number: 5, length: 16],
children: [
%{
key: :number,
type: :integer,
suggestions: [5],
description: "Number of backup codes to generate."
},
%{
key: :length,
type: :integer,
suggestions: [16],
description:
"Determines the length of backup one-time pass-codes, in characters. Defaults to 16 characters."
}
]
}
]
},
%{
key: :instance_thumbnail,
type: {:string, :image},
description:
"The instance thumbnail can be any image that represents your instance and is used by some apps or services when they display information about your instance.",
suggestions: ["/instance/thumbnail.jpeg"]
},
%{
key: :favicon,
type: {:string, :image},
description: "Favicon of the instance",
suggestions: ["/favicon.png"]
},
%{
key: :show_reactions,
type: :boolean,
description: "Let favourites and emoji reactions be viewed through the API."
},
%{
key: :profile_directory,
type: :boolean,
description: "Enable profile directory."
},
%{
key: :admin_privileges,
type: {:list, :atom},
suggestions: [
:users_read,
:users_manage_invites,
:users_manage_activation_state,
:users_manage_tags,
:users_manage_credentials,
:users_delete,
:messages_read,
:messages_delete,
:instances_delete,
:reports_manage_reports,
:moderation_log_read,
:announcements_manage_announcements,
:emoji_manage_emoji,
:statistics_read
],
description:
"What extra privileges to allow admins (e.g. updating user credentials, get password reset token, delete users, index and read private statuses and chats)"
},
%{
key: :moderator_privileges,
type: {:list, :atom},
suggestions: [
:users_read,
:users_manage_invites,
:users_manage_activation_state,
:users_manage_tags,
:users_manage_credentials,
:users_delete,
:messages_read,
:messages_delete,
:instances_delete,
:reports_manage_reports,
:moderation_log_read,
:announcements_manage_announcements,
:emoji_manage_emoji,
:statistics_read
],
description:
"What extra privileges to allow moderators (e.g. updating user credentials, get password reset token, delete users, index and read private statuses and chats)"
},
%{
key: :birthday_required,
type: :boolean,
description: "Require users to enter their birthday."
},
%{
key: :birthday_min_age,
type: :integer,
description:
"Minimum required age (in days) for users to create account. Only used if birthday is required.",
suggestions: [6570]
},
%{
key: :languages,
type: {:list, :string},
description:
"Languages to be exposed in /api/v1/instance. Should be in the format of BCP47 language codes.",
suggestions: [
"en"
]
}
]
},
%{
group: :pleroma,
key: :welcome,
type: :group,
description: "Welcome messages settings",
children: [
%{
key: :direct_message,
type: :keyword,
descpiption: "Direct message settings",
children: [
%{
key: :enabled,
type: :boolean,
description: "Enables sending a direct message to newly registered users"
},
%{
key: :message,
type: :string,
description: "A message that will be sent to newly registered users",
suggestions: [
"Hi, @username! Welcome on board!"
]
},
%{
key: :sender_nickname,
type: :string,
description: "The nickname of the local user that sends a welcome message",
suggestions: [
"lain"
]
}
]
},
%{
key: :chat_message,
type: :keyword,
descpiption: "Chat message settings",
children: [
%{
key: :enabled,
type: :boolean,
description: "Enables sending a chat message to newly registered users"
},
%{
key: :message,
type: :string,
description:
"A message that will be sent to newly registered users as a chat message",
suggestions: [
"Hello, welcome on board!"
]
},
%{
key: :sender_nickname,
type: :string,
description: "The nickname of the local user that sends a welcome chat message",
suggestions: [
"lain"
]
}
]
},
%{
key: :email,
type: :keyword,
descpiption: "Email message settings",
children: [
%{
key: :enabled,
type: :boolean,
description: "Enables sending an email to newly registered users"
},
%{
key: :sender,
type: [:string, :tuple],
description:
"Email address and/or nickname that will be used to send the welcome email.",
suggestions: [
{"Pleroma App", "welcome@pleroma.app"}
]
},
%{
key: :subject,
type: :string,
description:
"Subject of the welcome email. EEX template with user and instance_name variables can be used.",
suggestions: ["Welcome to <%= instance_name%>"]
},
%{
key: :html,
type: :string,
description:
"HTML content of the welcome email. EEX template with user and instance_name variables can be used.",
suggestions: ["<h1>Hello <%= user.name%>. Welcome to <%= instance_name%></h1>"]
},
%{
key: :text,
type: :string,
description:
"Text content of the welcome email. EEX template with user and instance_name variables can be used.",
suggestions: ["Hello <%= user.name%>. \n Welcome to <%= instance_name%>\n"]
}
]
}
]
},
%{
group: :pleroma,
key: :frontend_configurations,
type: :group,
description:
"This form can be used to configure a keyword list that keeps the configuration data for any " <>
"kind of frontend. By default, settings for pleroma_fe are configured. If you want to " <>
"add your own configuration your settings all fields must be complete.",
children: [
%{
key: :pleroma_fe,
label: "Pleroma FE",
type: :map,
description: "Settings for Pleroma FE",
suggestions: [
%{
alwaysShowSubjectInput: true,
background: "/static/aurora_borealis.jpg",
collapseMessageWithSubject: false,
greentext: false,
embeddedToS: true,
hideFilteredStatuses: false,
hideMutedPosts: false,
hidePostStats: false,
hideSitename: false,
hideUserStats: false,
loginMethod: "password",
logo: "/static/logo.svg",
logoMargin: ".1em",
logoMask: true,
minimalScopesMode: false,
noAttachmentLinks: false,
nsfwCensorImage: "/static/img/nsfw.74818f9.png",
postContentType: "text/plain",
redirectRootLogin: "/main/friends",
redirectRootNoLogin: "/main/all",
scopeCopy: true,
sidebarRight: false,
showFeaturesPanel: true,
showInstanceSpecificPanel: false,
subjectLineBehavior: "email",
theme: "pleroma-dark",
webPushNotifications: false
}
],
children: [
%{
key: :alwaysShowSubjectInput,
label: "Always show subject input",
type: :boolean,
description: "When disabled, auto-hide the subject field if it's empty"
},
%{
key: :background,
type: {:string, :image},
description:
"URL of the background, unless viewing a user profile with a background that is set",
suggestions: ["/images/city.jpg"]
},
%{
key: :collapseMessageWithSubject,
label: "Collapse message with subject",
type: :boolean,
description:
"When a message has a subject (aka Content Warning), collapse it by default"
},
%{
key: :greentext,
label: "Greentext",
type: :boolean,
description: "Enables green text on lines prefixed with the > character"
},
%{
key: :embeddedToS,
label: "Embedded ToS panel",
type: :boolean,
description: "Hide Terms of Service panel decorations on About and Registration pages"
},
%{
key: :hideFilteredStatuses,
label: "Hide Filtered Statuses",
type: :boolean,
description: "Hides filtered statuses from timelines"
},
%{
key: :hideMutedPosts,
label: "Hide Muted Posts",
type: :boolean,
description: "Hides muted statuses from timelines"
},
%{
key: :hidePostStats,
label: "Hide post stats",
type: :boolean,
description: "Hide notices statistics (repeats, favorites, ...)"
},
%{
key: :hideSitename,
label: "Hide Sitename",
type: :boolean,
description: "Hides instance name from PleromaFE banner"
},
%{
key: :hideUserStats,
label: "Hide user stats",
type: :boolean,
description:
"Hide profile statistics (posts, posts per day, followers, followings, ...)"
},
%{
key: :logo,
type: {:string, :image},
description: "URL of the logo, defaults to Pleroma's logo",
suggestions: ["/static/logo.svg"]
},
%{
key: :logoMargin,
label: "Logo margin",
type: :string,
description:
"Allows you to adjust vertical margins between logo boundary and navbar borders. " <>
"The idea is that to have logo's image without any extra margins and instead adjust them to your need in layout.",
suggestions: [".1em"]
},
%{
key: :logoMask,
label: "Logo mask",
type: :boolean,
description:
"By default it assumes logo used will be monochrome with alpha channel to be compatible with both light and dark themes. " <>
"If you want a colorful logo you must disable logoMask."
},
%{
key: :minimalScopesMode,
label: "Minimal scopes mode",
type: :boolean,
description:
"Limit scope selection to Direct, User default, and Scope of post replying to. " <>
"Also prevents replying to a DM with a public post from PleromaFE."
},
%{
key: :nsfwCensorImage,
label: "NSFW Censor Image",
type: {:string, :image},
description:
"URL of the image to use for hiding NSFW media attachments in the timeline",
suggestions: ["/static/img/nsfw.74818f9.png"]
},
%{
key: :postContentType,
label: "Post Content Type",
type: {:dropdown, :atom},
description: "Default post formatting option",
suggestions: ["text/plain", "text/html", "text/markdown", "text/bbcode"]
},
%{
key: :redirectRootNoLogin,
label: "Redirect root no login",
type: :string,
description:
"Relative URL which indicates where to redirect when a user isn't logged in",
suggestions: ["/main/all"]
},
%{
key: :redirectRootLogin,
label: "Redirect root login",
type: :string,
description:
"Relative URL which indicates where to redirect when a user is logged in",
suggestions: ["/main/friends"]
},
%{
key: :scopeCopy,
label: "Scope copy",
type: :boolean,
description: "Copy the scope (private/unlisted/public) in replies to posts by default"
},
%{
key: :sidebarRight,
label: "Sidebar on Right",
type: :boolean,
description: "Change alignment of sidebar and panels to the right"
},
%{
key: :showFeaturesPanel,
label: "Show instance features panel",
type: :boolean,
description:
"Enables panel displaying functionality of the instance on the About page"
},
%{
key: :showInstanceSpecificPanel,
label: "Show instance specific panel",
type: :boolean,
description: "Whether to show the instance's custom panel"
},
%{
key: :subjectLineBehavior,
label: "Subject line behavior",
type: :string,
description: "Allows changing the default behaviour of subject lines in replies.
`email`: copy and prepend re:, as in email,
`masto`: copy verbatim, as in Mastodon,
`noop`: don't copy the subject.",
suggestions: ["email", "masto", "noop"]
},
%{
key: :theme,
type: :string,
description: "Which theme to use. Available themes are defined in styles.json",
suggestions: ["pleroma-dark"]
}
]
}
]
},
%{
group: :pleroma,
key: :assets,
type: :group,
description:
"This section configures assets to be used with various frontends. Currently the only option relates to mascots on the mastodon frontend",
children: [
%{
key: :mascots,
type: {:keyword, :map},
description:
"Keyword of mascots, each element must contain both an URL and a mime_type key",
suggestions: [
pleroma_fox_tan: %{
url: "/images/pleroma-fox-tan-smol.png",
mime_type: "image/png"
},
pleroma_fox_tan_shy: %{
url: "/images/pleroma-fox-tan-shy.png",
mime_type: "image/png"
}
]
},
%{
key: :default_mascot,
type: :atom,
description:
"This will be used as the default mascot on MastoFE. Default: `:pleroma_fox_tan`",
suggestions: [
:pleroma_fox_tan
]
},
%{
key: :default_user_avatar,
type: {:string, :image},
description: "URL of the default user avatar",
suggestions: ["/images/avi.png"]
}
]
},
%{
group: :pleroma,
key: :manifest,
type: :group,
description:
"This section describe PWA manifest instance-specific values. Currently this option relate only for MastoFE.",
children: [
%{
key: :icons,
type: {:list, :map},
description: "Describe the icons of the app",
suggestion: [
%{
src: "/static/logo.png"
},
%{
src: "/static/icon.png",
type: "image/png"
},
%{
src: "/static/icon.ico",
sizes: "72x72 96x96 128x128 256x256"
}
]
},
%{
key: :theme_color,
type: :string,
description: "Describe the theme color of the app",
suggestions: ["#282c37", "mediumpurple"]
},
%{
key: :background_color,
type: :string,
description: "Describe the background color of the app",
suggestions: ["#191b22", "aliceblue"]
}
]
},
%{
group: :pleroma,
key: :media_proxy,
type: :group,
description: "Media proxy",
children: [
%{
key: :enabled,
type: :boolean,
description: "Enables proxying of remote media via the instance's proxy"
},
%{
key: :base_url,
label: "Base URL",
type: :string,
description:
"The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.",
suggestions: ["https://example.com"]
},
%{
key: :invalidation,
type: :keyword,
descpiption: "",
suggestions: [
enabled: true,
provider: Pleroma.Web.MediaProxy.Invalidation.Script
],
children: [
%{
key: :enabled,
type: :boolean,
description: "Enables media cache object invalidation."
},
%{
key: :provider,
type: :module,
description: "Module which will be used to purge objects from the cache.",
suggestions: [
Pleroma.Web.MediaProxy.Invalidation.Script,
Pleroma.Web.MediaProxy.Invalidation.Http
]
}
]
},
%{
key: :proxy_opts,
label: "Advanced MediaProxy Options",
type: :keyword,
description: "Internal Pleroma.ReverseProxy settings",
suggestions: [
redirect_on_failure: false,
max_body_length: 25 * 1_048_576,
max_read_duration: 30_000
],
children: [
%{
key: :redirect_on_failure,
type: :boolean,
description: """
Redirects the client to the origin server upon encountering HTTP errors.\n
Note that files larger than Max Body Length will trigger an error. (e.g., Peertube videos)\n\n
**WARNING:** This setting will allow larger files to be accessed, but exposes the\n
IP addresses of your users to the other servers, bypassing the MediaProxy.
"""
},
%{
key: :max_body_length,
type: :integer,
description:
"Maximum file size (in bytes) allowed through the Pleroma MediaProxy cache."
},
%{
key: :max_read_duration,
type: :integer,
description: "Timeout (in milliseconds) of GET request to the remote URI."
}
]
},
%{
key: :whitelist,
type: {:list, :string},
description: "List of hosts with scheme to bypass the MediaProxy",
suggestions: ["http://example.com"]
}
]
},
%{
group: :pleroma,
key: :media_preview_proxy,
type: :group,
description: "Media preview proxy",
children: [
%{
key: :enabled,
type: :boolean,
description:
"Enables proxying of remote media preview to the instance's proxy. Requires enabled media proxy."
},
%{
key: :thumbnail_max_width,
type: :integer,
description:
"Max width of preview thumbnail for images (video preview always has original dimensions)."
},
%{
key: :thumbnail_max_height,
type: :integer,
description:
"Max height of preview thumbnail for images (video preview always has original dimensions)."
},
%{
key: :image_quality,
type: :integer,
description: "Quality of the output. Ranges from 0 (min quality) to 100 (max quality)."
},
%{
key: :min_content_length,
type: :integer,
description:
"Min content length (in bytes) to perform preview. Media smaller in size will be served without thumbnailing."
}
]
},
%{
group: :pleroma,
key: Pleroma.Web.MediaProxy.Invalidation.Http,
type: :group,
description: "HTTP invalidate settings",
children: [
%{
key: :method,
type: :atom,
description: "HTTP method of request. Default: :purge"
},
%{
key: :headers,
type: {:keyword, :string},
description: "HTTP headers of request",
suggestions: [{"x-refresh", 1}]
},
%{
key: :options,
type: :keyword,
description: "Request options",
children: [
%{
key: :params,
type: {:map, :string}
}
]
}
]
},
%{
group: :pleroma,
key: Pleroma.Web.MediaProxy.Invalidation.Script,
type: :group,
description: "Invalidation script settings",
children: [
%{
key: :script_path,
type: :string,
description: "Path to executable script which will purge cached items.",
suggestions: ["./installation/nginx-cache-purge.sh.example"]
},
%{
key: :url_format,
label: "URL Format",
type: :string,
description:
"Optional URL format preprocessing. Only required for Apache's htcacheclean.",
suggestions: [":htcacheclean"]
}
]
},
%{
group: :pleroma,
key: :gopher,
type: :group,
description: "Gopher settings",
children: [
%{
key: :enabled,
type: :boolean,
description: "Enables the gopher interface"
},
%{
key: :ip,
label: "IP",
type: :tuple,
description: "IP address to bind to",
suggestions: [{0, 0, 0, 0}]
},
%{
key: :port,
type: :integer,
description: "Port to bind to",
suggestions: [9999]
},
%{
key: :dstport,
type: :integer,
description: "Port advertised in URLs (optional, defaults to port)",
suggestions: [9999]
}
]
},
%{
group: :pleroma,
key: :activitypub,
label: "ActivityPub",
type: :group,
description: "ActivityPub-related settings",
children: [
%{
key: :unfollow_blocked,
type: :boolean,
description: "Whether blocks result in people getting unfollowed"
},
%{
key: :outgoing_blocks,
type: :boolean,
description: "Whether to federate blocks to other instances"
},
%{
key: :blockers_visible,
type: :boolean,
description: "Whether a user can see someone who has blocked them"
},
%{
key: :sign_object_fetches,
type: :boolean,
description: "Sign object fetches with HTTP signatures"
},
%{
key: :authorized_fetch_mode,
type: :boolean,
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,
type: :integer,
description:
"The number of Note replies' URIs to be included with outgoing federation (`5` to match Mastodon hardcoded value, `0` to disable the output)"
},
%{
key: :follow_handshake_timeout,
type: :integer,
description: "Following handshake timeout",
suggestions: [500]
},
%{
key: :client_api_enabled,
type: :boolean,
description: "Allow client to server ActivityPub interactions"
},
%{
key: :anonymize_reporter,
type: :boolean,
label: "Anonymize local reports",
description:
"If true, replace local reporters with the designated local user for the copy to be sent to remote servers"
},
%{
key: :anonymize_reporter_local_nickname,
type: :string,
label: "Anonymized reporter",
description:
"The nickname of the designated local user that replaces the actual reporter in the copy to be sent to remote servers",
suggestions: [
"lain"
]
}
]
},
%{
group: :pleroma,
key: :http_security,
label: "HTTP security",
type: :group,
description: "HTTP security settings",
children: [
%{
key: :enabled,
type: :boolean,
description: "Whether the managed content security policy is enabled"
},
%{
key: :sts,
label: "STS",
type: :boolean,
description: "Whether to additionally send a Strict-Transport-Security header"
},
%{
key: :sts_max_age,
label: "STS max age",
type: :integer,
description: "The maximum age for the Strict-Transport-Security header if sent",
suggestions: [31_536_000]
},
%{
key: :ct_max_age,
label: "CT max age",
type: :integer,
description: "The maximum age for the Expect-CT header if sent",
suggestions: [2_592_000]
},
%{
key: :referrer_policy,
type: :string,
description: "The referrer policy to use, either \"same-origin\" or \"no-referrer\"",
suggestions: ["same-origin", "no-referrer"]
},
%{
key: :report_uri,
label: "Report URI",
type: :string,
description: "Adds the specified URL to report-uri and report-to group in CSP header",
suggestions: ["https://example.com/report-uri"]
}
]
},
%{
group: :web_push_encryption,
key: :vapid_details,
label: "Vapid Details",
type: :group,
description:
"Web Push Notifications configuration. You can use the mix task mix web_push.gen.keypair to generate it.",
children: [
%{
key: :subject,
type: :string,
description:
"A mailto link for the administrative contact." <>
" It's best if this email is not a personal email address, but rather a group email to the instance moderation team.",
suggestions: ["mailto:moderators@pleroma.com"]
},
%{
key: :public_key,
type: :string,
description: "VAPID public key",
suggestions: ["Public key"]
},
%{
key: :private_key,
type: :string,
description: "VAPID private key",
suggestions: ["Private key"]
}
]
},
%{
group: :pleroma,
key: Pleroma.Captcha,
type: :group,
description: "Captcha-related settings",
children: [
%{
key: :enabled,
type: :boolean,
description: "Whether the captcha should be shown on registration"
},
%{
key: :method,
type: :module,
description: "The method/service to use for captcha",
suggestions: [Pleroma.Captcha.Kocaptcha, Pleroma.Captcha.Native]
},
%{
key: :seconds_valid,
type: :integer,
description: "The time in seconds for which the captcha is valid",
suggestions: [60]
}
]
},
%{
group: :pleroma,
key: Pleroma.Captcha.Kocaptcha,
type: :group,
description:
"Kocaptcha is a very simple captcha service with a single API endpoint, the source code is" <>
" here: https://github.com/koto-bank/kocaptcha. The default endpoint (https://captcha.kotobank.ch) is hosted by the developer.",
children: [
%{
key: :endpoint,
type: :string,
description: "The kocaptcha endpoint to use",
suggestions: ["https://captcha.kotobank.ch"]
}
]
},
%{
group: :pleroma,
label: "Pleroma Admin Token",
type: :group,
description:
"Allows setting a token that can be used to authenticate requests with admin privileges without a normal user account token. Append the `admin_token` parameter to requests to utilize it. (Please reconsider using HTTP Basic Auth or OAuth-based authentication if possible)",
children: [
%{
key: :admin_token,
type: :string,
description: "Admin token",
suggestions: [
"Please use a high entropy string or UUID"
]
}
]
},
%{
group: :pleroma,
key: Oban,
type: :group,
description:
"[Oban](https://github.com/sorentwo/oban) asynchronous job processor configuration.",
children: [
%{
key: :log,
type: {:dropdown, :atom},
description: "Logs verbose mode",
suggestions: [false, :error, :warning, :info, :debug]
},
%{
key: :queues,
type: {:keyword, :integer},
description:
"Background jobs queues (keys: queues, values: max numbers of concurrent jobs)",
suggestions: [
activity_expiration: 10,
attachments_cleanup: 5,
background: 5,
federator_incoming: 50,
federator_outgoing: 50,
mailer: 10,
scheduled_activities: 10,
transmogrifier: 20,
web_push: 50
],
children: [
%{
key: :activity_expiration,
type: :integer,
description: "Activity expiration queue",
suggestions: [10]
},
%{
key: :backup,
type: :integer,
description: "Backup queue",
suggestions: [1]
},
%{
key: :attachments_cleanup,
type: :integer,
description: "Attachment deletion queue",
suggestions: [5]
},
%{
key: :background,
type: :integer,
description: "Background queue",
suggestions: [5]
},
%{
key: :federator_incoming,
type: :integer,
description: "Incoming federation queue",
suggestions: [50]
},
%{
key: :federator_outgoing,
type: :integer,
description: "Outgoing federation queue",
suggestions: [50]
},
%{
key: :mailer,
type: :integer,
description: "Email sender queue, see Pleroma.Emails.Mailer",
suggestions: [10]
},
%{
key: :scheduled_activities,
type: :integer,
description: "Scheduled activities queue, see Pleroma.ScheduledActivities",
suggestions: [10]
},
%{
key: :transmogrifier,
type: :integer,
description: "Transmogrifier queue",
suggestions: [20]
},
%{
key: :web_push,
type: :integer,
description: "Web push notifications queue",
suggestions: [50]
}
]
},
%{
key: :crontab,
type: {:list, :tuple},
description: "Settings for cron background jobs",
suggestions: [
{"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
{"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}
]
}
]
},
%{
group: :pleroma,
key: Pleroma.Web.Metadata,
type: :group,
description: "Metadata-related settings",
children: [
%{
key: :providers,
type: {:list, :module},
description: "List of metadata providers to enable",
suggestions: [
Pleroma.Web.Metadata.Providers.OpenGraph,
Pleroma.Web.Metadata.Providers.TwitterCard,
Pleroma.Web.Metadata.Providers.RelMe,
Pleroma.Web.Metadata.Providers.Feed
]
},
%{
key: :unfurl_nsfw,
label: "Unfurl NSFW",
type: :boolean,
description: "When enabled NSFW attachments will be shown in previews"
}
]
},
%{
group: :pleroma,
key: :rich_media,
type: :group,
description:
"If enabled the instance will parse metadata from attached links to generate link previews",
children: [
%{
key: :enabled,
type: :boolean,
description: "Enables RichMedia parsing of URLs"
},
%{
key: :ignore_hosts,
type: {:list, :string},
description: "List of hosts which will be ignored by the metadata parser",
suggestions: ["accounts.google.com", "xss.website"]
},
%{
key: :ignore_tld,
label: "Ignore TLD",
type: {:list, :string},
description: "List TLDs (top-level domains) which will ignore for parse metadata",
suggestions: ["local", "localdomain", "lan"]
},
%{
key: :parsers,
type: {:list, :module},
description:
"List of Rich Media parsers. Module names are shortened (removed leading `Pleroma.Web.RichMedia.Parsers.` part), but on adding custom module you need to use full name.",
suggestions: [
Pleroma.Web.RichMedia.Parsers.OEmbed,
Pleroma.Web.RichMedia.Parsers.TwitterCard
]
},
%{
key: :ttl_setters,
label: "TTL setters",
type: {:list, :module},
description:
"List of rich media TTL setters. Module names are shortened (removed leading `Pleroma.Web.RichMedia.Parser.` part), but on adding custom module you need to use full name.",
suggestions: [
Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl
]
},
%{
key: :timeout,
type: :integer,
description:
"Amount of milliseconds after which the HTTP request is forcibly terminated.",
suggestions: [5_000]
},
%{
key: :user_agent,
type: :string,
description: "Custom User-Agent header to be used when fetching rich media content."
}
]
},
%{
group: :pleroma,
key: Pleroma.Formatter,
label: "Linkify",
type: :group,
description:
"Configuration for Pleroma's link formatter which parses mentions, hashtags, and URLs.",
children: [
%{
key: :class,
type: [:string, :boolean],
description: "Specify the class to be added to the generated link. Disable to clear.",
suggestions: ["auto-linker", false]
},
%{
key: :rel,
type: [:string, :boolean],
description: "Override the rel attribute. Disable to clear.",
suggestions: ["ugc", "noopener noreferrer", false]
},
%{
key: :new_window,
type: :boolean,
description: "Link URLs will open in a new window/tab."
},
%{
key: :truncate,
type: [:integer, :boolean],
description:
"Set to a number to truncate URLs longer than the number. Truncated URLs will end in `...`",
suggestions: [15, false]
},
%{
key: :strip_prefix,
type: :boolean,
description: "Strip the scheme prefix."
},
%{
key: :extra,
type: :boolean,
description: "Link URLs with rarely used schemes (magnet, ipfs, irc, etc.)"
},
%{
key: :validate_tld,
type: [:atom, :boolean],
description:
"Set to false to disable TLD validation for URLs/emails. Can be set to :no_scheme to validate TLDs only for URLs without a scheme (e.g `example.com` will be validated, but `http://example.loki` won't)",
suggestions: [:no_scheme, true]
}
]
},
%{
group: :pleroma,
key: Pleroma.ScheduledActivity,
type: :group,
description: "Scheduled activities settings",
children: [
%{
key: :daily_user_limit,
type: :integer,
description:
"The number of scheduled activities a user is allowed to create in a single day. Default: 25.",
suggestions: [25]
},
%{
key: :total_user_limit,
type: :integer,
description:
"The number of scheduled activities a user is allowed to create in total. Default: 300.",
suggestions: [300]
},
%{
key: :enabled,
type: :boolean,
description: "Whether scheduled activities are sent to the job queue to be executed"
}
]
},
%{
group: :pleroma,
key: Pleroma.Workers.PurgeExpiredActivity,
type: :group,
description: "Expired activities settings",
children: [
%{
key: :enabled,
type: :boolean,
description: "Enables expired activities addition & deletion"
},
%{
key: :min_lifetime,
type: :integer,
description: "Minimum lifetime for ephemeral activity (in seconds)",
suggestions: [600]
}
]
},
%{
group: :pleroma,
label: "Pleroma Authenticator",
type: :group,
description: "Authenticator",
children: [
%{
key: Pleroma.Web.Auth.Authenticator,
type: :module,
suggestions: [Pleroma.Web.Auth.PleromaAuthenticator, Pleroma.Web.Auth.LDAPAuthenticator]
}
]
},
%{
group: :pleroma,
key: :ldap,
label: "LDAP",
type: :group,
description:
"Use LDAP for user authentication. When a user logs in to the Pleroma instance, the name and password" <>
" will be verified by trying to authenticate (bind) to a LDAP server." <>
" If a user exists in the LDAP directory but there is no account with the same name yet on the" <>
" Pleroma instance then a new Pleroma account will be created with the same name as the LDAP user name.",
children: [
%{
key: :enabled,
type: :boolean,
description: "Enables LDAP authentication"
},
%{
key: :host,
type: :string,
description: "LDAP server hostname",
suggestions: ["localhosts"]
},
%{
key: :port,
type: :integer,
description: "LDAP port, e.g. 389 or 636",
suggestions: [389, 636]
},
%{
key: :ssl,
label: "SSL",
type: :boolean,
description: "Enable to use SSL, usually implies the port 636"
},
%{
key: :sslopts,
label: "SSL options",
type: :keyword,
description: "Additional SSL options",
suggestions: [verify: :verify_peer],
children: [
%{
key: :verify,
type: :atom,
description: "Type of cert verification",
suggestions: [:verify_peer]
}
]
},
%{
key: :tls,
label: "TLS",
type: :boolean,
description: "Enable to use STARTTLS, usually implies the port 389"
},
%{
key: :tlsopts,
label: "TLS options",
type: :keyword,
description: "Additional TLS options",
suggestions: [verify: :verify_peer],
children: [
%{
key: :verify,
type: :atom,
description: "Type of cert verification",
suggestions: [:verify_peer]
}
]
},
%{
key: :base,
type: :string,
description: "LDAP base, e.g. \"dc=example,dc=com\"",
suggestions: ["dc=example,dc=com"]
},
%{
key: :uid,
label: "UID Attribute",
type: :string,
description:
"LDAP attribute name to authenticate the user, e.g. when \"cn\", the filter will be \"cn=username,base\"",
suggestions: ["cn"]
},
%{
key: :cacertfile,
label: "CACertfile",
type: :string,
description: "Path to CA certificate file"
},
%{
key: :mail,
label: "Mail Attribute",
type: :string,
description:
"LDAP attribute name to use as the email address when automatically registering the user on first login",
suggestions: ["mail"]
}
]
},
%{
group: :pleroma,
key: :auth,
type: :group,
description: "Authentication / authorization settings",
children: [
%{
key: :enforce_oauth_admin_scope_usage,
label: "Enforce OAuth admin scope usage",
type: :boolean,
description:
"OAuth admin scope requirement toggle. " <>
"If enabled, admin actions explicitly demand admin OAuth scope(s) presence in OAuth token " <>
"(client app must support admin scopes). If disabled and token doesn't have admin scope(s), " <>
"`is_admin` user flag grants access to admin-specific actions."
},
%{
key: :auth_template,
type: :string,
description:
"Authentication form template. By default it's `show.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/show.html.ee`.",
suggestions: ["show.html"]
},
%{
key: :oauth_consumer_template,
label: "OAuth consumer template",
type: :string,
description:
"OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to" <>
" `lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex`.",
suggestions: ["consumer.html"]
},
%{
key: :oauth_consumer_strategies,
label: "OAuth consumer strategies",
type: {:list, :string},
description:
"The list of enabled OAuth consumer strategies. By default it's set by OAUTH_CONSUMER_STRATEGIES environment variable." <>
" Each entry in this space-delimited string should be of format \"strategy\" or \"strategy:dependency\"" <>
" (e.g. twitter or keycloak:ueberauth_keycloak_strategy in case dependency is named differently than ueberauth_<strategy>).",
suggestions: ["twitter", "keycloak:ueberauth_keycloak_strategy"]
}
]
},
%{
group: :pleroma,
key: :email_notifications,
type: :group,
description: "Email notifications settings",
children: [
%{
key: :digest,
type: :map,
description:
"emails of \"what you've missed\" for users who have been inactive for a while",
suggestions: [
%{
active: false,
schedule: "0 0 * * 0",
interval: 7,
inactivity_threshold: 7
}
],
children: [
%{
key: :active,
label: "Enabled",
type: :boolean,
description: "Globally enable or disable digest emails"
},
%{
key: :schedule,
type: :string,
description:
"When to send digest email, in crontab format. \"0 0 0\" is the default, meaning \"once a week at midnight on Sunday morning\".",
suggestions: ["0 0 * * 0"]
},
%{
key: :interval,
type: :integer,
description: "Minimum interval between digest emails to one user",
suggestions: [7]
},
%{
key: :inactivity_threshold,
type: :integer,
description: "Minimum user inactivity threshold",
suggestions: [7]
}
]
}
]
},
%{
group: :pleroma,
key: Pleroma.Emails.UserEmail,
type: :group,
description: "Email template settings",
children: [
%{
key: :logo,
type: {:string, :image},
description: "A path to a custom logo. Set it to `nil` to use the default Pleroma logo.",
suggestions: ["some/path/logo.png"]
},
%{
key: :styling,
type: :map,
description: "A map with color settings for email templates.",
suggestions: [
%{
link_color: "#d8a070",
background_color: "#2C3645",
content_background_color: "#1B2635",
header_color: "#d8a070",
text_color: "#b9b9ba",
text_muted_color: "#b9b9ba"
}
],
children: [
%{
key: :link_color,
type: :string,
suggestions: ["#d8a070"]
},
%{
key: :background_color,
type: :string,
suggestions: ["#2C3645"]
},
%{
key: :content_background_color,
type: :string,
suggestions: ["#1B2635"]
},
%{
key: :header_color,
type: :string,
suggestions: ["#d8a070"]
},
%{
key: :text_color,
type: :string,
suggestions: ["#b9b9ba"]
},
%{
key: :text_muted_color,
type: :string,
suggestions: ["#b9b9ba"]
}
]
}
]
},
%{
group: :pleroma,
key: Pleroma.Emails.NewUsersDigestEmail,
type: :group,
description: "New users admin email digest",
children: [
%{
key: :enabled,
type: :boolean,
description: "Enables new users admin digest email when `true`"
}
]
},
%{
group: :pleroma,
key: :oauth2,
label: "OAuth2",
type: :group,
description: "Configure OAuth 2 provider capabilities",
children: [
%{
key: :token_expires_in,
type: :integer,
description: "The lifetime in seconds of the access token",
suggestions: [2_592_000]
},
%{
key: :issue_new_refresh_token,
type: :boolean,
description:
"Keeps old refresh token or generate new refresh token when to obtain an access token"
},
%{
key: :clean_expired_tokens,
type: :boolean,
description: "Enable a background job to clean expired OAuth tokens. Default: disabled."
}
]
},
%{
group: :pleroma,
key: :emoji,
type: :group,
children: [
%{
key: :shortcode_globs,
type: {:list, :string},
description: "Location of custom emoji files. * can be used as a wildcard.",
suggestions: ["/emoji/custom/**/*.png"]
},
%{
key: :pack_extensions,
type: {:list, :string},
description:
"A list of file extensions for emojis, when no emoji.txt for a pack is present",
suggestions: [".png", ".gif"]
},
%{
key: :groups,
type: {:keyword, {:list, :string}},
description:
"Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the group name" <>
" and the value is the location or array of locations. * can be used as a wildcard.",
suggestions: [
Custom: ["/emoji/*.png", "/emoji/**/*.png"]
]
},
%{
key: :default_manifest,
type: :string,
description:
"Location of the JSON-manifest. This manifest contains information about the emoji-packs you can download." <>
" Currently only one manifest can be added (no arrays).",
suggestions: ["https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json"]
},
%{
key: :shared_pack_cache_seconds_per_file,
label: "Shared pack cache s/file",
type: :integer,
descpiption:
"When an emoji pack is shared, the archive is created and cached in memory" <>
" for this amount of seconds multiplied by the number of files.",
suggestions: [60]
}
]
},
%{
group: :pleroma,
key: :rate_limit,
type: :group,
description:
"Rate limit settings. This is an advanced feature enabled only for :authentication by default.",
children: [
%{
key: :search,
type: [:tuple, {:list, :tuple}],
description: "For the search requests (account & status search etc.)",
suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
},
%{
key: :timeline,
type: [:tuple, {:list, :tuple}],
description: "For requests to timelines (each timeline has it's own limiter)",
suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
},
%{
key: :app_account_creation,
type: [:tuple, {:list, :tuple}],
description: "For registering user accounts from the same IP address",
suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
},
%{
key: :relations_actions,
type: [:tuple, {:list, :tuple}],
description: "For actions on relationships with all users (follow, unfollow)",
suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
},
%{
key: :relation_id_action,
label: "Relation ID action",
type: [:tuple, {:list, :tuple}],
description: "For actions on relation with a specific user (follow, unfollow)",
suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
},
%{
key: :statuses_actions,
type: [:tuple, {:list, :tuple}],
description:
"For create / delete / fav / unfav / reblog / unreblog actions on any statuses",
suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
},
%{
key: :status_id_action,
label: "Status ID action",
type: [:tuple, {:list, :tuple}],
description:
"For fav / unfav or reblog / unreblog actions on the same status by the same user",
suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
},
%{
key: :authentication,
type: [:tuple, {:list, :tuple}],
description: "For authentication create / password check / user existence check requests",
suggestions: [{60_000, 15}]
}
]
},
%{
group: :mime,
label: "Mime Types",
type: :group,
description: "Mime Types settings",
children: [
%{
key: :types,
type: :map,
suggestions: [
%{
"application/xml" => ["xml"],
"application/xrd+xml" => ["xrd+xml"],
"application/jrd+json" => ["jrd+json"],
"application/activity+json" => ["activity+json"],
"application/ld+json" => ["activity+json"]
}
],
children: [
%{
key: "application/xml",
type: {:list, :string},
suggestions: ["xml"]
},
%{
key: "application/xrd+xml",
type: {:list, :string},
suggestions: ["xrd+xml"]
},
%{
key: "application/jrd+json",
type: {:list, :string},
suggestions: ["jrd+json"]
},
%{
key: "application/activity+json",
type: {:list, :string},
suggestions: ["activity+json"]
},
%{
key: "application/ld+json",
type: {:list, :string},
suggestions: ["activity+json"]
}
]
}
]
},
%{
group: :pleroma,
key: :shout,
type: :group,
description: "Pleroma shout settings",
children: [
%{
key: :enabled,
type: :boolean,
description: "Enables the backend Shoutbox chat feature."
},
%{
key: :limit,
type: :integer,
description: "Shout message character limit.",
suggestions: [
5_000
]
}
]
},
%{
group: :pleroma,
key: :http,
label: "HTTP",
type: :group,
description: "HTTP settings",
children: [
%{
key: :proxy_url,
label: "Proxy URL",
type: [:string, :tuple],
description: "Proxy URL",
suggestions: ["localhost:9020", {:socks5, :localhost, 3090}]
},
%{
key: :send_user_agent,
type: :boolean
},
%{
key: :user_agent,
type: [:string, :atom],
description:
"What user agent to use. Must be a string or an atom `:default`. Default value is `:default`.",
suggestions: ["Pleroma", :default]
},
%{
key: :adapter,
type: :keyword,
description: "Adapter specific options",
suggestions: [],
children: [
%{
key: :ssl_options,
type: :keyword,
label: "SSL Options",
description: "SSL options for HTTP adapter",
children: [
%{
key: :versions,
type: {:list, :atom},
description: "List of TLS version to use",
suggestions: [:tlsv1, ":tlsv1.1", ":tlsv1.2", ":tlsv1.3"]
}
]
}
]
}
]
},
%{
group: :pleroma,
key: :markup,
label: "Markup Settings",
type: :group,
children: [
%{
key: :allow_inline_images,
type: :boolean
},
%{
key: :allow_headings,
type: :boolean
},
%{
key: :allow_tables,
type: :boolean
},
%{
key: :allow_fonts,
type: :boolean
},
%{
key: :scrub_policy,
type: {:list, :module},
description:
"Module names are shortened (removed leading `Pleroma.HTML.` part), but on adding custom module you need to use full name.",
suggestions: [Pleroma.HTML.Transform.MediaProxy, Pleroma.HTML.Scrubber.Default]
}
]
},
%{
group: :pleroma,
key: :user,
type: :group,
children: [
%{
key: :deny_follow_blocked,
type: :boolean
}
]
},
%{
group: :pleroma,
key: Pleroma.User,
type: :group,
children: [
%{
key: :restricted_nicknames,
type: {:list, :string},
description: "List of nicknames users may not register with.",
suggestions: [
".well-known",
"~",
"about",
"activities",
"api",
"auth",
"check_password",
"dev",
"friend-requests",
"inbox",
"internal",
"main",
"media",
"nodeinfo",
"notice",
"oauth",
"objects",
"ostatus_subscribe",
"pleroma",
"proxy",
"push",
"registration",
"relay",
"settings",
"status",
"tag",
"user-search",
"user_exists",
"users",
"web"
]
},
%{
key: :email_blacklist,
type: {:list, :string},
description: "List of email domains users may not register with.",
suggestions: ["mailinator.com", "maildrop.cc"]
}
]
},
%{
group: :cors_plug,
label: "CORS plug config",
type: :group,
children: [
%{
key: :max_age,
type: :integer,
suggestions: [86_400]
},
%{
key: :methods,
type: {:list, :string},
suggestions: ["POST", "PUT", "DELETE", "GET", "PATCH", "OPTIONS"]
},
%{
key: :expose,
type: {:list, :string},
suggestions: [
"Link",
"X-RateLimit-Reset",
"X-RateLimit-Limit",
"X-RateLimit-Remaining",
"X-Request-Id",
"Idempotency-Key"
]
},
%{
key: :credentials,
type: :boolean
},
%{
key: :headers,
type: {:list, :string},
suggestions: ["Authorization", "Content-Type", "Idempotency-Key"]
}
]
},
%{
group: :pleroma,
key: Pleroma.Web.Plugs.RemoteIp,
type: :group,
description: """
`Pleroma.Web.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
**If your instance is not behind at least one reverse proxy, you should not enable this plug.**
""",
children: [
%{
key: :enabled,
type: :boolean,
description: "Enable/disable the plug. Default: disabled."
},
%{
key: :headers,
type: {:list, :string},
description: """
A list of strings naming the HTTP headers to use when deriving the true client IP. Default: `["x-forwarded-for"]`.
"""
},
%{
key: :proxies,
type: {:list, :string},
description:
"A list of upstream proxy IP subnets in CIDR notation from which we will parse the content of `headers`. Defaults to `[]`. IPv4 entries without a bitmask will be assumed to be /32 and IPv6 /128."
},
%{
key: :reserved,
type: {:list, :string},
description: """
A list of reserved IP subnets in CIDR notation which should be ignored if found in `headers`. Defaults to `["127.0.0.0/8", "::1/128", "fc00::/7", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]`
"""
}
]
},
%{
group: :pleroma,
key: :web_cache_ttl,
label: "Web cache TTL",
type: :group,
description:
"The expiration time for the web responses cache. Values should be in milliseconds or `nil` to disable expiration.",
children: [
%{
key: :activity_pub,
type: :integer,
description:
"Activity pub routes (except question activities). Default: `nil` (no expiration).",
suggestions: [nil]
},
%{
key: :activity_pub_question,
type: :integer,
description: "Activity pub routes (question activities). Default: `30_000` (30 seconds).",
suggestions: [30_000]
}
]
},
%{
group: :pleroma,
key: :static_fe,
label: "Static FE",
type: :group,
description:
"Render profiles and posts using server-generated HTML that is viewable without using JavaScript",
children: [
%{
key: :enabled,
type: :boolean,
description: "Enables the rendering of static HTML. Default: disabled."
}
]
},
%{
group: :pleroma,
key: :feed,
type: :group,
description: "Configure feed rendering",
children: [
%{
key: :post_title,
type: :map,
description: "Configure title rendering",
children: [
%{
key: :max_length,
type: :integer,
description: "Maximum number of characters before truncating title",
suggestions: [100]
},
%{
key: :omission,
type: :string,
description: "Replacement which will be used after truncating string",
suggestions: ["..."]
}
]
}
]
},
%{
group: :pleroma,
key: :mrf_follow_bot,
tab: :mrf,
related_policy: "Pleroma.Web.ActivityPub.MRF.FollowBotPolicy",
label: "MRF FollowBot Policy",
type: :group,
description: "Automatically follows newly discovered accounts.",
children: [
%{
key: :follower_nickname,
type: :string,
description: "The name of the bot account to use for following newly discovered users.",
suggestions: ["followbot"]
}
]
},
%{
group: :pleroma,
key: :modules,
type: :group,
description: "Custom Runtime Modules",
children: [
%{
key: :runtime_dir,
type: :string,
description: "A path to custom Elixir modules (such as MRF policies)."
}
]
},
%{
group: :pleroma,
key: :streamer,
type: :group,
description: "Settings for notifications streamer",
children: [
%{
key: :workers,
type: :integer,
description: "Number of workers to send notifications",
suggestions: [3]
},
%{
key: :overflow_workers,
type: :integer,
description: "Maximum number of workers created if pool is empty",
suggestions: [2]
}
]
},
%{
group: :pleroma,
key: :connections_pool,
type: :group,
description: "Advanced settings for `Gun` connections pool",
children: [
%{
key: :connection_acquisition_wait,
type: :integer,
description:
"Timeout to acquire a connection from pool. The total max time is this value multiplied by the number of retries. Default: 250ms.",
suggestions: [250]
},
%{
key: :connection_acquisition_retries,
type: :integer,
description:
"Number of attempts to acquire the connection from the pool if it is overloaded. Default: 5",
suggestions: [5]
},
%{
key: :max_connections,
type: :integer,
description: "Maximum number of connections in the pool. Default: 250 connections.",
suggestions: [250]
},
%{
key: :connect_timeout,
type: :integer,
description: "Timeout while `gun` will wait until connection is up. Default: 5000ms.",
suggestions: [5000]
},
%{
key: :reclaim_multiplier,
type: :integer,
description:
"Multiplier for the number of idle connection to be reclaimed if the pool is full. For example if the pool maxes out at 250 connections and this setting is set to 0.3, the pool will reclaim at most 75 idle connections if it's overloaded. Default: 0.1",
suggestions: [0.1]
}
]
},
%{
group: :pleroma,
key: :pools,
type: :group,
description: "Advanced settings for `Gun` workers pools",
children:
Enum.map([:federation, :media, :upload, :default], fn pool_name ->
%{
key: pool_name,
type: :keyword,
description: "Settings for #{pool_name} pool.",
children: [
%{
key: :size,
type: :integer,
description: "Maximum number of concurrent requests in the pool.",
suggestions: [50]
},
%{
key: :max_waiting,
type: :integer,
description:
"Maximum number of requests waiting for other requests to finish. After this number is reached, the pool will start returning errors when a new request is made",
suggestions: [10]
},
%{
key: :recv_timeout,
type: :integer,
description: "Timeout for the pool while gun will wait for response",
suggestions: [10_000]
}
]
}
end)
},
%{
group: :pleroma,
key: :hackney_pools,
type: :group,
description: "Advanced settings for `Hackney` connections pools",
children: [
%{
key: :federation,
type: :keyword,
description: "Settings for federation pool.",
children: [
%{
key: :max_connections,
type: :integer,
description: "Number workers in the pool.",
suggestions: [50]
},
%{
key: :timeout,
type: :integer,
description: "Timeout while `hackney` will wait for response.",
suggestions: [150_000]
}
]
},
%{
key: :media,
type: :keyword,
description: "Settings for media pool.",
children: [
%{
key: :max_connections,
type: :integer,
description: "Number workers in the pool.",
suggestions: [50]
},
%{
key: :timeout,
type: :integer,
description: "Timeout while `hackney` will wait for response.",
suggestions: [150_000]
}
]
},
%{
key: :upload,
type: :keyword,
description: "Settings for upload pool.",
children: [
%{
key: :max_connections,
type: :integer,
description: "Number workers in the pool.",
suggestions: [25]
},
%{
key: :timeout,
type: :integer,
description: "Timeout while `hackney` will wait for response.",
suggestions: [300_000]
}
]
}
]
},
%{
group: :pleroma,
key: :restrict_unauthenticated,
label: "Restrict Unauthenticated",
type: :group,
description:
"Disallow viewing timelines, user profiles and statuses for unauthenticated users.",
children: [
%{
key: :timelines,
type: :map,
description: "Settings for public and federated timelines.",
children: [
%{
key: :local,
type: :boolean,
description: "Disallow view public timeline."
},
%{
key: :federated,
type: :boolean,
description: "Disallow view federated timeline."
}
]
},
%{
key: :profiles,
type: :map,
description: "Settings for user profiles.",
children: [
%{
key: :local,
type: :boolean,
description: "Disallow view local user profiles."
},
%{
key: :remote,
type: :boolean,
description: "Disallow view remote user profiles."
}
]
},
%{
key: :activities,
type: :map,
description: "Settings for statuses.",
children: [
%{
key: :local,
type: :boolean,
description: "Disallow view local statuses."
},
%{
key: :remote,
type: :boolean,
description: "Disallow view remote statuses."
}
]
}
]
},
%{
group: :pleroma,
key: Pleroma.Web.ApiSpec.CastAndValidate,
type: :group,
children: [
%{
key: :strict,
type: :boolean,
description:
"Enables strict input validation (useful in development, not recommended in production)"
}
]
},
%{
group: :pleroma,
key: :instances_favicons,
type: :group,
description: "Control favicons for instances",
children: [
%{
key: :enabled,
type: :boolean,
description: "Allow/disallow displaying and getting instances favicons"
}
]
},
%{
group: :ex_aws,
key: :s3,
type: :group,
descriptions: "S3 service related settings",
children: [
%{
key: :access_key_id,
type: :string,
description: "S3 access key ID",
suggestions: ["AKIAQ8UKHTGIYN7DMWWJ"]
},
%{
key: :secret_access_key,
type: :string,
description: "Secret access key",
suggestions: ["JFGt+fgH1UQ7vLUQjpW+WvjTdV/UNzVxcwn7DkaeFKtBS5LvoXvIiME4NQBsT6ZZ"]
},
%{
key: :host,
type: :string,
description: "S3 host",
suggestions: ["s3.eu-central-1.amazonaws.com"]
},
%{
key: :region,
type: :string,
description: "S3 region (for AWS)",
suggestions: ["us-east-1"]
}
]
},
%{
group: :pleroma,
key: :frontends,
type: :group,
description: "Installed frontends management",
children: [
%{
key: :primary,
type: :map,
description: "Primary frontend, the one that is served for all pages by default",
children: installed_frontend_options
},
%{
key: :admin,
type: :map,
description: "Admin frontend",
children: installed_frontend_options
},
%{
key: :available,
type: :map,
description:
"A map containing available frontends and parameters for their installation.",
children: frontend_options
},
%{
key: :pickable,
type: {:list, :string},
description:
"A list containing all frontends users can pick as their preference, format is :name/:ref, e.g pleroma-fe/stable."
}
]
},
%{
group: :pleroma,
key: Pleroma.Web.Preload,
type: :group,
description: "Preload-related settings",
children: [
%{
key: :providers,
type: {:list, :module},
description: "List of preload providers to enable",
suggestions: [
Pleroma.Web.Preload.Providers.Instance,
Pleroma.Web.Preload.Providers.User,
Pleroma.Web.Preload.Providers.Timelines
]
}
]
},
%{
group: :pleroma,
key: :majic_pool,
type: :group,
description: "Majic/libmagic configuration",
children: [
%{
key: :size,
type: :integer,
description: "Number of majic workers to start.",
suggestions: [2]
}
]
},
%{
group: :pleroma,
key: Pleroma.User.Backup,
type: :group,
description: "Account Backup",
children: [
%{
key: :purge_after_days,
type: :integer,
description: "Remove backup archives after N days",
suggestions: [30]
},
%{
key: :limit_days,
type: :integer,
description: "Limit user to export not more often than once per N days",
suggestions: [7]
},
%{
key: :process_chunk_size,
type: :integer,
label: "Process Chunk Size",
description: "The number of activities to fetch in the backup job for each chunk.",
suggestions: [100]
},
%{
key: :timeout,
type: :integer,
label: "Timeout",
description: "The amount of time to wait for backup to complete in seconds.",
suggestions: [1_800]
}
]
},
%{
group: :prometheus,
key: Pleroma.Web.Endpoint.MetricsExporter,
type: :group,
description: "Prometheus app metrics endpoint configuration",
children: [
%{
key: :enabled,
type: :boolean,
description: "[Pleroma extension] Enables app metrics endpoint."
},
%{
key: :ip_whitelist,
label: "IP Whitelist",
type: [{:list, :string}, {:list, :charlist}, {:list, :tuple}],
description: "Restrict access of app metrics endpoint to the specified IP addresses."
},
%{
key: :auth,
type: [:boolean, :tuple],
description: "Enables HTTP Basic Auth for app metrics endpoint.",
suggestion: [false, {:basic, "myusername", "mypassword"}]
},
%{
key: :path,
type: :string,
description: "App metrics endpoint URI path.",
suggestions: ["/api/pleroma/app_metrics"]
},
%{
key: :format,
type: :atom,
description: "App metrics endpoint output format.",
suggestions: [:text, :protobuf]
}
]
},
%{
group: :pleroma,
key: ConcurrentLimiter,
type: :group,
description: "Limits configuration for background tasks.",
children: [
%{
key: Pleroma.Web.RichMedia.Helpers,
type: :keyword,
description: "Concurrent limits configuration for getting RichMedia for activities.",
suggestions: [max_running: 5, max_waiting: 5],
children: [
%{
key: :max_running,
type: :integer,
description: "Max running concurrently jobs.",
suggestion: [5]
},
%{
key: :max_waiting,
type: :integer,
description: "Max waiting jobs.",
suggestion: [5]
}
]
},
%{
key: Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy,
type: :keyword,
description: "Concurrent limits configuration for MediaProxyWarmingPolicy.",
suggestions: [max_running: 5, max_waiting: 5],
children: [
%{
key: :max_running,
type: :integer,
description: "Max running concurrently jobs.",
suggestion: [5]
},
%{
key: :max_waiting,
type: :integer,
description: "Max waiting jobs.",
suggestion: [5]
}
]
}
]
},
%{
group: :pleroma,
key: Pleroma.Search,
type: :group,
description: "General search settings.",
children: [
%{
key: :module,
type: :keyword,
description: "Selected search module.",
suggestion: [
Pleroma.Search.DatabaseSearch,
Pleroma.Search.Meilisearch,
Pleroma.Search.QdrantSearch,
Pleroma.Search.ParadeDB
]
}
]
},
%{
group: :pleroma,
key: Pleroma.Search.Meilisearch,
type: :group,
description: "Meilisearch settings.",
children: [
%{
key: :url,
type: :string,
description: "Meilisearch URL.",
suggestion: ["http://127.0.0.1:7700/"]
},
%{
key: :private_key,
type: :string,
description:
"Private key for meilisearch authentication, or `nil` to disable private key authentication.",
suggestion: [nil]
},
%{
key: :initial_indexing_chunk_size,
type: :integer,
description:
"Amount of posts in a batch when running the initial indexing operation. Should probably not be more than 100000" <>
" since there's a limit on maximum insert size",
suggestion: [100_000]
}
]
},
%{
group: :pleroma,
key: Pleroma.Search.ParadeDB,
type: :group,
description: "ParadeDB settings.",
children: [
%{
key: :url,
type: :string,
description:
"ParadeDB database URL (separate Postgres instance). " <>
"Can also be set via `PARADEDB_DATABASE_URL`.",
suggestion: ["postgres://postgres:postgres@127.0.0.1:5432/paradedb"]
},
%{
key: :table,
type: :string,
description: "Table name used for search documents.",
suggestion: ["pleroma_search_documents"]
+ },
+ %{
+ key: :fuzzy_distance,
+ type: :integer,
+ description: "Edit-distance used for fuzzy token matching (0 disables fuzziness, max 2).",
+ suggestion: [0, 1, 2]
}
]
},
%{
group: :pleroma,
key: Pleroma.Language.LanguageDetector,
type: :group,
description: "Language detection providers",
children: [
%{
key: :provider,
type: :module,
suggestions: {:list_behaviour_implementations, Pleroma.Language.LanguageDetector.Provider}
},
%{
group: {:subgroup, Pleroma.Language.LanguageDetector.Fasttext},
key: :model,
label: "fastText language detection model",
type: :string,
suggestions: ["/usr/share/fasttext/lid.176.bin"]
}
]
},
%{
group: :pleroma,
key: Pleroma.Language.Translation,
type: :group,
description: "Translation providers",
children: [
%{
key: :provider,
type: :module,
suggestions: {:list_behaviour_implementations, Pleroma.Language.Translation.Provider}
},
%{
group: {:subgroup, Pleroma.Language.Translation.Deepl},
key: :base_url,
label: "DeepL base URL",
type: :string,
suggestions: ["https://api-free.deepl.com", "https://api.deepl.com"]
},
%{
group: {:subgroup, Pleroma.Language.Translation.Deepl},
key: :api_key,
label: "DeepL API Key",
type: :string,
suggestions: ["YOUR_API_KEY"]
},
%{
group: {:subgroup, Pleroma.Language.Translation.Libretranslate},
key: :base_url,
label: "LibreTranslate instance URL",
type: :string,
suggestions: ["https://libretranslate.com"]
},
%{
group: {:subgroup, Pleroma.Language.Translation.Libretranslate},
key: :api_key,
label: "LibreTranslate API Key",
type: :string,
suggestions: ["YOUR_API_KEY"]
},
%{
group: {:subgroup, Pleroma.Language.Translation.TranslateLocally},
key: :intermediary_language,
label:
"translateLocally intermediary language (used when direct source->target model is not available)",
type: :string,
suggestions: ["en"]
},
%{
group: {:subgroup, Pleroma.Language.Translation.Mozhi},
key: :base_url,
label: "Mozhi instance URL",
type: :string
},
%{
group: {:subgroup, Pleroma.Language.Translation.Mozhi},
key: :engine,
label: "Engine used for Mozhi",
type: :string,
suggestions: ["libretranslate"]
}
]
}
]
diff --git a/docs/configuration/search.md b/docs/configuration/search.md
index 780904d10..c0d3f8462 100644
--- a/docs/configuration/search.md
+++ b/docs/configuration/search.md
@@ -1,181 +1,184 @@
# Configuring search
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
## Built-in search
To use built-in search that has no external dependencies, set the search module to `Pleroma.Activity`:
> config :pleroma, Pleroma.Search, module: Pleroma.Search.DatabaseSearch
While it has no external dependencies, it has problems with performance and relevancy.
## QdrantSearch
This uses the vector search engine [Qdrant](https://qdrant.tech) to search the posts in a vector space. This needs a way to generate embeddings and uses the [OpenAI API](https://platform.openai.com/docs/guides/embeddings/what-are-embeddings). This is implemented by several project besides OpenAI itself, including the python-based fastembed-server found in `supplemental/search/fastembed-api`.
The default settings will support a setup where both the fastembed server and Qdrant run on the same system as pleroma. To use it, set the search provider and run the fastembed server, see the README in `supplemental/search/fastembed-api`:
> config :pleroma, Pleroma.Search, module: Pleroma.Search.QdrantSearch
Then, start the Qdrant server, see [here](https://qdrant.tech/documentation/quick-start/) for instructions.
You will also need to create the Qdrant index once by running `mix pleroma.search.indexer create_index`. Running `mix pleroma.search.indexer index` will retroactively index the last 100_000 activities.
### Indexing and model options
To see the available configuration options, check out the QdrantSearch section in `config/config.exs`.
The default indexing option work for the default model (`snowflake-arctic-embed-xs`). To optimize for a low memory footprint, adjust the index configuration as described in the [Qdrant docs](https://qdrant.tech/documentation/guides/optimize/). See also [this blog post](https://qdrant.tech/articles/memory-consumption/) that goes into detail.
Different embedding models will need different vector size settings. You can see a list of the models supported by the fastembed server [here](https://qdrant.github.io/fastembed/examples/Supported_Models), including their vector dimensions. These vector dimensions need to be set in the `qdrant_index_configuration`.
E.g, If you want to use `sentence-transformers/all-MiniLM-L6-v2` as a model, you will not need to adjust things, because it and `snowflake-arctic-embed-xs` are both 384 dimensional models. If you want to use `snowflake/snowflake-arctic-embed-l`, you will need to adjust the `size` parameter in the `qdrant_index_configuration` to 1024, as it has a dimension of 1024.
When using a different model, you will need do drop the index and recreate it (`mix pleroma.search.indexer drop_index` and `mix pleroma.search.indexer create_index`), as the different embeddings are not compatible with each other.
## Meilisearch
Note that it's quite a bit more memory hungry than PostgreSQL (around 4-5G for ~1.2 million
posts while idle and up to 7G while indexing initially). The disk usage for this additional index is also
around 4 gigabytes. Like [RUM](./cheatsheet.md#rum-indexing-for-full-text-search) indexes, it offers considerably
higher performance and ordering by timestamp in a reasonable amount of time.
Additionally, the search results seem to be more accurate.
Due to high memory usage, it may be best to set it up on a different machine, if running pleroma on a low-resource
computer, and use private key authentication to secure the remote search instance.
To use [meilisearch](https://www.meilisearch.com/), set the search module to `Pleroma.Search.Meilisearch`:
> config :pleroma, Pleroma.Search, module: Pleroma.Search.Meilisearch
You then need to set the address of the meilisearch instance, and optionally the private key for authentication. You might
also want to change the `initial_indexing_chunk_size` to be smaller if you're server is not very powerful, but not higher than `100_000`,
because meilisearch will refuse to process it if it's too big. However, in general you want this to be as big as possible, because meilisearch
indexes faster when it can process many posts in a single batch.
> config :pleroma, Pleroma.Search.Meilisearch,
> url: "http://127.0.0.1:7700/",
> private_key: "private key",
> initial_indexing_chunk_size: 100_000
Information about setting up meilisearch can be found in the
[official documentation](https://docs.meilisearch.com/learn/getting_started/installation.html).
You probably want to start it with `MEILI_NO_ANALYTICS=true` environment variable to disable analytics.
At least version 0.25.0 is required, but you are strongly advised to use at least 0.26.0, as it introduces
the `--enable-auto-batching` option which drastically improves performance. Without this option, the search
is hardly usable on a somewhat big instance.
### Private key authentication (optional)
To set the private key, use the `MEILI_MASTER_KEY` environment variable when starting. After setting the _master key_,
you have to get the _private key_, which is actually used for authentication.
=== "OTP"
```sh
./bin/pleroma_ctl search.meilisearch show-keys <your master key here>
```
=== "From Source"
```sh
mix pleroma.search.meilisearch show-keys <your master key here>
```
You will see a "Default Admin API Key", this is the key you actually put into your configuration file.
### Initial indexing
After setting up the configuration, you'll want to index all of your already existing posts. Only public posts are indexed. You'll only
have to do it one time, but it might take a while, depending on the amount of posts your instance has seen. This is also a fairly RAM
consuming process for `meilisearch`, and it will take a lot of RAM when running if you have a lot of posts (seems to be around 5G for ~1.2
million posts while idle and up to 7G while indexing initially, but your experience may be different).
The sequence of actions is as follows:
1. First, change the configuration to use `Pleroma.Search.Meilisearch` as the search backend
2. Restart your instance, at this point it can be used while the search indexing is running, though search won't return anything
3. Start the initial indexing process (as described below with `index`),
and wait until the task says it sent everything from the database to index
4. Wait until everything is actually indexed (by checking with `stats` as described below),
at this point you don't have to do anything, just wait a while.
To start the initial indexing, run the `index` command:
=== "OTP"
```sh
./bin/pleroma_ctl search.meilisearch index
```
=== "From Source"
```sh
mix pleroma.search.meilisearch index
```
This will show you the total amount of posts to index, and then show you the amount of posts indexed currently, until the numbers eventually
become the same. The posts are indexed in big batches and meilisearch will take some time to actually index them, even after you have
inserted all the posts into it. Depending on the amount of posts, this may be as long as several hours. To get information about the status
of indexing and how many posts have actually been indexed, use the `stats` command:
=== "OTP"
```sh
./bin/pleroma_ctl search.meilisearch stats
```
=== "From Source"
```sh
mix pleroma.search.meilisearch stats
```
### Clearing the index
In case you need to clear the index (for example, to re-index from scratch, if that needs to happen for some reason), you can
use the `clear` command:
=== "OTP"
```sh
./bin/pleroma_ctl search.meilisearch clear
```
=== "From Source"
```sh
mix pleroma.search.meilisearch clear
```
This will clear **all** the posts from the search index. Note, that deleted posts are also removed from index by the instance itself, so
there is no need to actually clear the whole index, unless you want **all** of it gone. That said, the index does not hold any information
that cannot be re-created from the database, it should also generally be a lot smaller than the size of your database. Still, the size
depends on the amount of text in posts.
## ParadeDB
[ParadeDB](https://www.paradedb.com/) is a Postgres extension that provides BM25 full-text search. In Pleroma, it can be used as an
external search backend by pointing Pleroma at a separate Postgres instance with the `pg_search` extension installed.
Pleroma will maintain a small `pleroma_search_documents` table in that database (via the existing search indexing queue) and run search
queries against it.
To use it, set the search module to `Pleroma.Search.ParadeDB` and configure the ParadeDB database URL:
> config :pleroma, Pleroma.Search, module: Pleroma.Search.ParadeDB
>
> config :pleroma, Pleroma.Search.ParadeDB,
> url: System.get_env("PARADEDB_DATABASE_URL") || "postgres://postgres:postgres@127.0.0.1:5432/paradedb",
-> table: "pleroma_search_documents"
+> table: "pleroma_search_documents",
+> fuzzy_distance: 0
Then, create the table and BM25 index once:
=== "From Source"
```sh
mix pleroma.search.indexer create_index
```
After that, run an initial indexing pass to backfill existing posts:
=== "From Source"
```sh
mix pleroma.search.indexer index
```
Note: Like the other external search backends, only public/unlisted Notes are indexed.
+Set `fuzzy_distance` to `1` or `2` to allow typo-tolerant matching (`2` is the maximum ParadeDB supports).
+
Indexing runs via the Oban `search_indexing` queue, so ensure that queue is enabled (not paused) in your Oban configuration.
diff --git a/lib/pleroma/search/paradedb.ex b/lib/pleroma/search/paradedb.ex
index 01cbc323f..57e5599b5 100644
--- a/lib/pleroma/search/paradedb.ex
+++ b/lib/pleroma/search/paradedb.ex
@@ -1,266 +1,287 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Search.ParadeDB do
@behaviour Pleroma.Search.SearchBackend
require Logger
require Pleroma.Constants
import Ecto.Query
import Pleroma.Search.DatabaseSearch,
only: [
maybe_fetch: 3,
maybe_restrict_author: 2,
maybe_restrict_blocked: 2,
maybe_restrict_local: 2
]
import Pleroma.Search.Meilisearch, only: [object_to_search_data: 1]
alias Pleroma.Activity
alias Pleroma.Config.Getting, as: Config
@max_limit 40
@default_table "pleroma_search_documents"
defp client_impl do
Config.get([Pleroma.Search.ParadeDB, :client_impl]) ||
Pleroma.Search.ParadeDB.Client.Postgres
end
defp table do
Config.get([Pleroma.Search.ParadeDB, :table], @default_table)
end
+ defp fuzzy_distance do
+ case Config.get([Pleroma.Search.ParadeDB, :fuzzy_distance], 0) do
+ value when is_integer(value) and value in 0..2 ->
+ value
+
+ value when is_binary(value) ->
+ case Integer.parse(value) do
+ {parsed, ""} when parsed in 0..2 -> parsed
+ _ -> 0
+ end
+
+ _ ->
+ 0
+ end
+ end
+
@impl true
def create_index do
with {:ok, _} <- client_impl().query("CREATE EXTENSION IF NOT EXISTS pg_search", []),
{:ok, _} <- client_impl().query(create_table_sql(table()), []),
{:ok, _} <- client_impl().query(create_object_id_index_sql(table()), []),
{:ok, _} <- client_impl().query(create_bm25_index_sql(table()), []) do
:ok
else
{:error, error} ->
Logger.error("ParadeDB create_index failed: #{inspect(error)}")
{:error, error}
end
end
@impl true
def drop_index do
with {:ok, _} <- client_impl().query("DROP TABLE IF EXISTS #{table()}", []) do
:ok
else
{:error, error} ->
Logger.error("ParadeDB drop_index failed: #{inspect(error)}")
{:error, error}
end
end
@impl true
def healthcheck_endpoints, do: nil
@impl true
def add_to_index(activity) do
maybe_search_data = object_to_search_data(activity.object)
if activity.data["type"] == "Create" and maybe_search_data do
dumped_activity_id = dump_activity_id(activity.id)
actor_ap_id = activity.data["actor"]
published_at = DateTime.from_unix!(maybe_search_data.published)
if is_nil(dumped_activity_id) do
Logger.error("ParadeDB add_to_index failed: invalid activity id #{inspect(activity.id)}")
:ok
else
sql =
"""
INSERT INTO #{table()} (id, object_id, object_ap_id, actor_ap_id, content, published_at)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (id) DO UPDATE SET
object_id = EXCLUDED.object_id,
object_ap_id = EXCLUDED.object_ap_id,
actor_ap_id = EXCLUDED.actor_ap_id,
content = EXCLUDED.content,
published_at = EXCLUDED.published_at
"""
params = [
dumped_activity_id,
maybe_search_data.id,
maybe_search_data.ap,
actor_ap_id,
maybe_search_data.content,
published_at
]
case client_impl().query(sql, params) do
{:ok, _} ->
:ok
{:error, error} ->
Logger.error("ParadeDB add_to_index failed: #{inspect(error)}")
{:error, error}
end
end
else
:ok
end
end
@impl true
def remove_from_index(object) do
sql = "DELETE FROM #{table()} WHERE object_id = $1"
case client_impl().query(sql, [object.id]) do
{:ok, _} ->
:ok
{:error, error} ->
Logger.error("ParadeDB remove_from_index failed: #{inspect(error)}")
{:error, error}
end
end
@impl true
def search(user, query, options \\ []) do
limit = options |> Keyword.get(:limit, @max_limit) |> min(@max_limit)
offset = Keyword.get(options, :offset, 0)
author = Keyword.get(options, :author)
case search_ids(query, limit, offset, author) do
{:ok, []} ->
maybe_fetch([], user, query)
{:ok, ids} ->
ids
|> fetch_activities(user, query, author)
{:error, error} ->
Logger.error("ParadeDB search failed: #{inspect(error)}")
maybe_fetch([], user, query)
end
end
defp fetch_activities(ids, user, query, author) do
dumped_ids =
ids
|> Enum.map(&dump_activity_id/1)
|> Enum.reject(&is_nil/1)
from(a in Activity, where: a.id in ^dumped_ids)
|> Activity.with_preloaded_object()
|> Activity.restrict_deactivated_users()
|> maybe_restrict_public(user)
|> maybe_restrict_local(user)
|> maybe_restrict_author(author)
|> maybe_restrict_blocked(user)
|> order_by(
[a],
fragment(
"array_position(?, ?)",
^dumped_ids,
a.id
)
)
|> Pleroma.Repo.all()
|> maybe_fetch(user, query)
end
defp maybe_restrict_public(query, %Pleroma.User{}) do
intended_recipients = [
Pleroma.Constants.as_public(),
Pleroma.Web.ActivityPub.Utils.as_local_public()
]
from(a in query,
where: fragment("?->>'type' = 'Create'", a.data),
where: fragment("? && ?", ^intended_recipients, a.recipients)
)
end
defp maybe_restrict_public(query, _user) do
from(a in query,
where: fragment("?->>'type' = 'Create'", a.data),
where: ^Pleroma.Constants.as_public() in a.recipients
)
end
defp search_ids(query, limit, offset, author) do
- base_sql = "SELECT id FROM #{table()} WHERE content &&& $1"
+ base_sql =
+ "SELECT id FROM #{table()} WHERE content &&& #{search_query_param_sql(fuzzy_distance())}"
+
params = [query]
{base_sql, params} =
case author do
%Pleroma.User{ap_id: ap_id} when is_binary(ap_id) ->
{base_sql <> " AND actor_ap_id = $2", params ++ [ap_id]}
_ ->
{base_sql, params}
end
limit_arg = length(params) + 1
offset_arg = length(params) + 2
sql =
base_sql <>
" ORDER BY published_at DESC, id DESC LIMIT $#{limit_arg} OFFSET $#{offset_arg}"
params = params ++ [limit, offset]
with {:ok, %{rows: rows}} <- client_impl().query(sql, params) do
{:ok, Enum.map(rows, fn [id] -> id end)}
end
end
+ defp search_query_param_sql(0), do: "$1"
+ defp search_query_param_sql(distance), do: "$1::pdb.fuzzy(#{distance})"
+
defp create_table_sql(table) do
"""
CREATE TABLE IF NOT EXISTS #{table} (
id uuid PRIMARY KEY,
object_id bigint NOT NULL,
object_ap_id text NOT NULL,
actor_ap_id text,
content text NOT NULL,
published_at timestamptz
)
"""
end
defp create_object_id_index_sql(table) do
"CREATE INDEX IF NOT EXISTS #{table}_object_id_idx ON #{table} (object_id)"
end
defp create_bm25_index_sql(table) do
"""
CREATE INDEX IF NOT EXISTS #{table}_bm25_idx ON #{table}
USING bm25 (id, content, (actor_ap_id::pdb.literal), published_at)
WITH (key_field = 'id')
"""
end
defp dump_activity_id(<<_::binary-size(16)>> = uuid), do: uuid
defp dump_activity_id(id) when is_binary(id) do
case Ecto.UUID.dump(id) do
{:ok, <<_::binary-size(16)>> = uuid} ->
uuid
:error ->
dump_flake_activity_id(id)
end
end
defp dump_activity_id(_), do: nil
defp dump_flake_activity_id(id) do
case FlakeId.Ecto.CompatType.dump(id) do
{:ok, <<_::binary-size(16)>> = uuid} -> uuid
_ -> nil
end
rescue
_ -> nil
end
end
diff --git a/test/pleroma/search/paradedb_integration_test.exs b/test/pleroma/search/paradedb_integration_test.exs
index 42d12c4d6..743dcbaff 100644
--- a/test/pleroma/search/paradedb_integration_test.exs
+++ b/test/pleroma/search/paradedb_integration_test.exs
@@ -1,240 +1,240 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Search.ParadeDBIntegrationTest do
use Pleroma.DataCase, async: false
@moduletag :integration
@moduletag timeout: 180_000
import Pleroma.Factory
alias Pleroma.Activity
alias Pleroma.Search.ParadeDB
alias Pleroma.Web.CommonAPI
@image System.get_env("PARADEDB_TEST_IMAGE", "paradedb/paradedb:latest")
@db_user "postgres"
@db_password "postgres"
@db_name "paradedb"
@docker_startup_timeout_ms 60_000
setup do
Mox.stub_with(Pleroma.UnstubbedConfigMock, Pleroma.Config)
:ok
end
setup_all do
with :ok <- ensure_docker_available() do
container = "pleroma-paradedb-it-#{System.unique_integer([:positive])}"
on_exit(fn ->
_ = docker(["rm", "-f", container])
end)
{id, 0} =
docker([
"run",
"--rm",
"--name",
container,
"-e",
"POSTGRES_USER=#{@db_user}",
"-e",
"POSTGRES_PASSWORD=#{@db_password}",
"-e",
"POSTGRES_DB=#{@db_name}",
"-p",
"127.0.0.1::5432",
"-d",
@image
])
container_id = String.trim(id)
host_port =
container
|> wait_for_port!(@docker_startup_timeout_ms)
wait_for_pg_ready!(container, @docker_startup_timeout_ms)
wait_for_tcp_ready!(host_port, @docker_startup_timeout_ms)
url = "postgres://#{@db_user}:#{@db_password}@127.0.0.1:#{host_port}/#{@db_name}"
prev_env = System.get_env("PARADEDB_DATABASE_URL")
System.put_env("PARADEDB_DATABASE_URL", url)
on_exit(fn ->
if is_nil(prev_env) do
System.delete_env("PARADEDB_DATABASE_URL")
else
System.put_env("PARADEDB_DATABASE_URL", prev_env)
end
end)
- {:ok, _pid} = start_supervised({Pleroma.Search.ParadeDB.Repo, pool_size: 1})
+ {:ok, _pid} = start_supervised({Pleroma.Search.ParadeDB.Repo, pool_size: 5})
previous_table = Pleroma.Config.get([Pleroma.Search.ParadeDB, :table], nil)
table = "pleroma_search_documents_it_#{System.unique_integer([:positive])}"
Pleroma.Config.put([Pleroma.Search.ParadeDB, :table], table)
on_exit(fn ->
if is_nil(previous_table) do
Pleroma.Config.delete([Pleroma.Search.ParadeDB, :table])
else
Pleroma.Config.put([Pleroma.Search.ParadeDB, :table], previous_table)
end
end)
{:ok,
%{
container: container,
container_id: container_id,
url: url,
table: table
}}
end
end
test "indexes and searches against real ParadeDB" do
:ok = ParadeDB.drop_index()
assert :ok = ParadeDB.create_index()
user = insert(:user)
{:ok, a1} =
CommonAPI.post(user, %{status: "paradedb integration first", visibility: "public"})
:timer.sleep(1100)
{:ok, a2} =
CommonAPI.post(user, %{status: "paradedb integration second", visibility: "public"})
{:ok, private} =
CommonAPI.post(user, %{status: "paradedb integration private", visibility: "private"})
a1 = Activity.get_by_id_with_object(a1.id)
a2 = Activity.get_by_id_with_object(a2.id)
private = Activity.get_by_id_with_object(private.id)
assert :ok = ParadeDB.add_to_index(a1)
assert :ok = ParadeDB.add_to_index(a2)
assert :ok = ParadeDB.add_to_index(private)
results = ParadeDB.search(nil, "paradedb integration", limit: 40, offset: 0)
ids = Enum.map(results, & &1.id)
assert a1.id in ids
assert a2.id in ids
refute private.id in ids
assert List.first(ids) == a2.id
# Removing an object should remove it from the ParadeDB index.
assert :ok = ParadeDB.remove_from_index(a2.object)
results_after_delete = ParadeDB.search(nil, "paradedb integration", limit: 40, offset: 0)
ids_after_delete = Enum.map(results_after_delete, & &1.id)
assert a1.id in ids_after_delete
refute a2.id in ids_after_delete
end
defp ensure_docker_available do
if is_nil(System.find_executable("docker")) do
{:skip, "Docker not installed"}
else
case docker(["info"]) do
{_out, 0} -> :ok
{out, _} -> {:skip, "Docker not available: #{String.trim(out)}"}
end
end
end
defp wait_for_port!(container, timeout_ms) do
deadline = System.monotonic_time(:millisecond) + timeout_ms
wait = fn wait ->
case docker(["port", container, "5432/tcp"]) do
{out, 0} when byte_size(out) > 0 ->
out
|> String.split("\n", trim: true)
|> List.first()
|> parse_host_port!()
_ ->
if System.monotonic_time(:millisecond) > deadline do
raise "Timed out waiting for docker port mapping"
else
:timer.sleep(250)
wait.(wait)
end
end
end
wait.(wait)
end
defp parse_host_port!(mapping) do
mapping
|> String.trim()
|> String.split(":")
|> List.last()
|> String.to_integer()
end
defp wait_for_pg_ready!(container, timeout_ms) do
deadline = System.monotonic_time(:millisecond) + timeout_ms
wait = fn wait ->
case docker([
"exec",
container,
"pg_isready",
"-U",
@db_user,
"-d",
@db_name
]) do
{_out, 0} ->
:ok
_ ->
if System.monotonic_time(:millisecond) > deadline do
raise "Timed out waiting for ParadeDB Postgres to become ready"
else
:timer.sleep(250)
wait.(wait)
end
end
end
wait.(wait)
end
defp wait_for_tcp_ready!(port, timeout_ms) do
deadline = System.monotonic_time(:millisecond) + timeout_ms
wait = fn wait ->
case :gen_tcp.connect(~c"127.0.0.1", port, [:binary, active: false], 500) do
{:ok, socket} ->
:gen_tcp.close(socket)
:ok
{:error, _} ->
if System.monotonic_time(:millisecond) > deadline do
raise "Timed out waiting for TCP port #{port} to accept connections"
else
:timer.sleep(250)
wait.(wait)
end
end
end
wait.(wait)
end
defp docker(args) do
System.cmd("docker", args, stderr_to_stdout: true)
end
end
diff --git a/test/pleroma/search/paradedb_test.exs b/test/pleroma/search/paradedb_test.exs
index c9bb216df..c4fb57cb3 100644
--- a/test/pleroma/search/paradedb_test.exs
+++ b/test/pleroma/search/paradedb_test.exs
@@ -1,239 +1,280 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Search.ParadeDBTest do
use Pleroma.DataCase, async: true
use Oban.Testing, repo: Pleroma.Repo
import Mox
import Pleroma.Factory
alias Pleroma.Search.ParadeDB
alias Pleroma.Search.ParadeDB.ClientMock
alias Pleroma.UnstubbedConfigMock, as: Config
alias Pleroma.Web.CommonAPI
alias Pleroma.Workers.SearchIndexingWorker
describe "ParadeDB" do
test "indexes a public post on creation" do
user = insert(:user)
{:ok, activity} =
CommonAPI.post(user, %{
status: "guys i just don't wanna leave the swamp",
visibility: "public"
})
args = %{"op" => "add_to_index", "activity" => activity.id}
assert_enqueued(worker: SearchIndexingWorker, args: args)
ClientMock
|> expect(:query, fn sql, params ->
assert sql =~ "INSERT INTO pleroma_search_documents"
[activity_id, object_id, object_ap_id, actor_ap_id, content, published_at] = params
{:ok, dumped_activity_id} = FlakeId.Ecto.CompatType.dump(activity.id)
assert activity_id == dumped_activity_id
assert is_integer(object_id)
assert is_binary(object_ap_id)
assert actor_ap_id == activity.data["actor"]
assert content == "guys i just don&#39;t wanna leave the swamp"
assert %DateTime{} = published_at
send(self(), :inserted)
{:ok, %{}}
end)
Config
|> expect(:get, 3, fn
[Pleroma.Search, :module], nil ->
ParadeDB
[Pleroma.Search.ParadeDB, :client_impl], nil ->
ClientMock
[Pleroma.Search.ParadeDB, :table], "pleroma_search_documents" ->
"pleroma_search_documents"
end)
assert :ok = perform_job(SearchIndexingWorker, args)
assert_received(:inserted)
end
test "doesn't index posts that are not public" do
user = insert(:user)
Enum.each(["private", "direct"], fn visibility ->
{:ok, activity} =
CommonAPI.post(user, %{
status: "guys i just don't wanna leave the swamp",
visibility: visibility
})
args = %{"op" => "add_to_index", "activity" => activity.id}
assert_enqueued(worker: SearchIndexingWorker, args: args)
Config
|> expect(:get, fn
[Pleroma.Search, :module], nil ->
ParadeDB
end)
assert :ok = perform_job(SearchIndexingWorker, args)
end)
end
test "deletes posts from index when deleted locally" do
user = insert(:user)
ClientMock
|> expect(:query, 2, fn sql, params ->
cond do
String.contains?(sql, "INSERT INTO pleroma_search_documents") ->
send(self(), :inserted)
{:ok, %{}}
String.contains?(sql, "DELETE FROM pleroma_search_documents") ->
assert [object_id] = params
assert is_integer(object_id)
send(self(), :deleted)
{:ok, %{}}
true ->
flunk("Unexpected ParadeDB SQL: #{inspect(sql)}")
end
end)
Config
|> expect(:get, 6, fn
[Pleroma.Search, :module], nil ->
ParadeDB
[Pleroma.Search.ParadeDB, :client_impl], nil ->
ClientMock
[Pleroma.Search.ParadeDB, :table], "pleroma_search_documents" ->
"pleroma_search_documents"
end)
{:ok, activity} =
CommonAPI.post(user, %{
status: "guys i just don't wanna leave the swamp",
visibility: "public"
})
args = %{"op" => "add_to_index", "activity" => activity.id}
assert_enqueued(worker: SearchIndexingWorker, args: args)
assert :ok = perform_job(SearchIndexingWorker, args)
assert_received(:inserted)
{:ok, _} = CommonAPI.delete(activity.id, user)
delete_args = %{"op" => "remove_from_index", "object" => activity.object.id}
assert_enqueued(worker: SearchIndexingWorker, args: delete_args)
assert :ok = perform_job(SearchIndexingWorker, delete_args)
assert_received(:deleted)
end
test "search returns activities in backend order" do
user = insert(:user)
{:ok, activity1} = CommonAPI.post(user, %{status: "first swamp", visibility: "public"})
{:ok, activity2} = CommonAPI.post(user, %{status: "second swamp", visibility: "public"})
activity1_id = activity1.id
activity2_id = activity2.id
{:ok, dumped_activity1_id} = FlakeId.Ecto.CompatType.dump(activity1_id)
{:ok, dumped_activity2_id} = FlakeId.Ecto.CompatType.dump(activity2_id)
ClientMock
|> expect(:query, fn sql, params ->
assert sql =~ "SELECT id FROM pleroma_search_documents"
assert ["swamp", _limit, _offset] = params
{:ok, %{rows: [[dumped_activity2_id], [dumped_activity1_id]]}}
end)
Config
- |> expect(:get, 2, fn
+ |> expect(:get, 3, fn
[Pleroma.Search.ParadeDB, :client_impl], nil ->
ClientMock
[Pleroma.Search.ParadeDB, :table], "pleroma_search_documents" ->
"pleroma_search_documents"
+
+ [Pleroma.Search.ParadeDB, :fuzzy_distance], 0 ->
+ 0
end)
assert [%{id: ^activity2_id}, %{id: ^activity1_id}] =
ParadeDB.search(nil, "swamp", limit: 40, offset: 0)
end
test "search excludes non-public activities from backend hits" do
user = insert(:user)
{:ok, public_activity} =
CommonAPI.post(user, %{status: "public swamp", visibility: "public"})
{:ok, private_activity} =
CommonAPI.post(user, %{status: "private swamp", visibility: "private"})
public_activity_id = public_activity.id
{:ok, dumped_public_id} = FlakeId.Ecto.CompatType.dump(public_activity.id)
{:ok, dumped_private_id} = FlakeId.Ecto.CompatType.dump(private_activity.id)
ClientMock
|> expect(:query, fn sql, params ->
assert sql =~ "SELECT id FROM pleroma_search_documents"
assert ["swamp", _limit, _offset] = params
{:ok, %{rows: [[dumped_private_id], [dumped_public_id]]}}
end)
Config
- |> expect(:get, 2, fn
+ |> expect(:get, 3, fn
[Pleroma.Search.ParadeDB, :client_impl], nil ->
ClientMock
[Pleroma.Search.ParadeDB, :table], "pleroma_search_documents" ->
"pleroma_search_documents"
+
+ [Pleroma.Search.ParadeDB, :fuzzy_distance], 0 ->
+ 0
end)
assert [%{id: ^public_activity_id}] = ParadeDB.search(nil, "swamp", limit: 40, offset: 0)
end
test "search supports backend ids returned as uuid strings" do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{status: "uuid swamp", visibility: "public"})
activity_id = activity.id
{:ok, dumped_id} = FlakeId.Ecto.CompatType.dump(activity_id)
{:ok, uuid_string} = Ecto.UUID.load(dumped_id)
ClientMock
|> expect(:query, fn sql, params ->
assert sql =~ "SELECT id FROM pleroma_search_documents"
assert ["swamp", _limit, _offset] = params
{:ok, %{rows: [[uuid_string]]}}
end)
Config
- |> expect(:get, 2, fn
+ |> expect(:get, 3, fn
[Pleroma.Search.ParadeDB, :client_impl], nil ->
ClientMock
[Pleroma.Search.ParadeDB, :table], "pleroma_search_documents" ->
"pleroma_search_documents"
+
+ [Pleroma.Search.ParadeDB, :fuzzy_distance], 0 ->
+ 0
end)
assert [%{id: ^activity_id}] = ParadeDB.search(nil, "swamp", limit: 40, offset: 0)
end
+
+ test "search uses configured fuzzy distance" do
+ user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{status: "runing shoes", visibility: "public"})
+
+ activity_id = activity.id
+ {:ok, dumped_id} = FlakeId.Ecto.CompatType.dump(activity_id)
+
+ ClientMock
+ |> expect(:query, fn sql, params ->
+ assert sql =~ "SELECT id FROM pleroma_search_documents"
+ assert sql =~ "$1::pdb.fuzzy(1)"
+ assert ["running shoes", _limit, _offset] = params
+
+ {:ok, %{rows: [[dumped_id]]}}
+ end)
+
+ Config
+ |> expect(:get, 3, fn
+ [Pleroma.Search.ParadeDB, :client_impl], nil ->
+ ClientMock
+
+ [Pleroma.Search.ParadeDB, :table], "pleroma_search_documents" ->
+ "pleroma_search_documents"
+
+ [Pleroma.Search.ParadeDB, :fuzzy_distance], 0 ->
+ 1
+ end)
+
+ assert [%{id: ^activity_id}] = ParadeDB.search(nil, "running shoes", limit: 40, offset: 0)
+ end
end
end

File Metadata

Mime Type
text/x-diff
Expires
Fri, Oct 30, 9:43 PM (14 h, 52 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1800579
Default Alt Text
(169 KB)

Event Timeline