Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
255 KB
Referenced Files
None
Subscribers
None
diff --git a/Dockerfile b/Dockerfile
index 11a17b7ad..edaa22a52 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,60 +1,60 @@
# https://hub.docker.com/r/hexpm/elixir/tags
ARG ELIXIR_IMG=hexpm/elixir
ARG ELIXIR_VER=1.17.3
ARG ERLANG_VER=26.2.5.6
ARG ALPINE_VER=3.17.9
FROM ${ELIXIR_IMG}:${ELIXIR_VER}-erlang-${ERLANG_VER}-alpine-${ALPINE_VER} AS build
COPY . .
ENV MIX_ENV=prod
ENV VIX_COMPILATION_MODE=PLATFORM_PROVIDED_LIBVIPS
RUN apk add git gcc g++ musl-dev make cmake file-dev vips-dev &&\
echo "import Config" > config/prod.secret.exs &&\
mix local.hex --force &&\
mix local.rebar --force &&\
mix deps.clean --all &&\
mix deps.get --only prod &&\
mkdir release &&\
mix release --path release
FROM alpine:${ALPINE_VER}
ARG BUILD_DATE
ARG VCS_REF
LABEL maintainer="ops@pleroma.social" \
org.opencontainers.image.title="pleroma" \
org.opencontainers.image.description="Pleroma for Docker" \
org.opencontainers.image.authors="ops@pleroma.social" \
org.opencontainers.image.vendor="pleroma.social" \
org.opencontainers.image.documentation="https://git.pleroma.social/pleroma/pleroma" \
org.opencontainers.image.licenses="AGPL-3.0" \
org.opencontainers.image.url="https://pleroma.social" \
org.opencontainers.image.revision=$VCS_REF \
org.opencontainers.image.created=$BUILD_DATE
ARG HOME=/opt/pleroma
ARG DATA=/var/lib/pleroma
RUN apk update &&\
- apk add exiftool ffmpeg vips libmagic ncurses postgresql-client &&\
+ apk add ffmpeg vips libmagic ncurses postgresql-client &&\
adduser --system --shell /bin/false --home ${HOME} pleroma &&\
mkdir -p ${DATA}/uploads &&\
mkdir -p ${DATA}/static &&\
chown -R pleroma ${DATA} &&\
mkdir -p /etc/pleroma &&\
chown -R pleroma /etc/pleroma
USER pleroma
COPY --from=build --chown=pleroma:0 /release ${HOME}
COPY --chown=pleroma --chmod=640 ./config/docker.exs /etc/pleroma/config.exs
COPY ./docker-entrypoint.sh ${HOME}
EXPOSE 4000
ENTRYPOINT ["/opt/pleroma/docker-entrypoint.sh"]
diff --git a/docs/administration/CLI_tasks/instance.md b/docs/administration/CLI_tasks/instance.md
index 88509cf5b..74e962140 100644
--- a/docs/administration/CLI_tasks/instance.md
+++ b/docs/administration/CLI_tasks/instance.md
@@ -1,45 +1,45 @@
# Managing instance configuration
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
## Generate a new configuration file
=== "OTP"
```sh
./bin/pleroma_ctl instance gen [option ...]
```
=== "From Source"
```sh
mix pleroma.instance gen [option ...]
```
If any of the options are left unspecified, you will be prompted interactively.
### Options
- `-f`, `--force` - overwrite any output files
- `-o <path>`, `--output <path>` - the output file for the generated configuration
- `--output-psql <path>` - the output file for the generated PostgreSQL setup
- `--domain <domain>` - the domain of your instance
- `--instance-name <instance_name>` - the name of your instance
- `--admin-email <email>` - the email address of the instance admin
- `--notify-email <email>` - email address for notifications
- `--dbhost <hostname>` - the hostname of the PostgreSQL database to use
- `--dbname <database_name>` - the name of the database to use
- `--dbuser <username>` - the user (aka role) to use for the database connection
- `--dbpass <password>` - the password to use for the database connection
- `--rum <Y|N>` - Whether to enable RUM indexes
- `--indexable <Y|N>` - Allow/disallow indexing site by search engines
- `--db-configurable <Y|N>` - Allow/disallow configuring instance from admin part
- `--uploads-dir <path>` - the directory uploads go in when using a local uploader
- `--static-dir <path>` - the directory custom public files should be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)
- `--listen-ip <ip>` - the ip the app should listen to, defaults to 127.0.0.1
- `--listen-port <port>` - the port the app should listen to, defaults to 4000
-- `--strip-uploads-location <Y|N>` - use ExifTool to strip uploads of sensitive location data
-- `--read-uploads-description <Y|N>` - use ExifTool to read image descriptions from uploads
+- `--strip-uploads-location <Y|N>` - strip uploads of sensitive location data
+- `--read-uploads-description <Y|N>` - read image descriptions from uploads so clients can prefill the media description field
- `--anonymize-uploads <Y|N>` - randomize uploaded filenames
- `--dedupe-uploads <Y|N>` - store files based on their hash to reduce data storage requirements if duplicates are uploaded with different filenames
- `--skip-release-env` - skip generation the release environment file
- `--release-env-file` - release environment file path
diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
index 54dd4a5f0..cf4c6ea7c 100644
--- a/docs/configuration/cheatsheet.md
+++ b/docs/configuration/cheatsheet.md
@@ -1,1243 +1,1243 @@
# Configuration Cheat Sheet
This is a cheat sheet for Pleroma configuration file, any setting possible to configure should be listed here.
For OTP installations the configuration is typically stored in `/etc/pleroma/config.exs`.
For from source installations Pleroma configuration works by first importing the base config `config/config.exs`, then overriding it by the environment config `config/$MIX_ENV.exs` and then overriding it by user config `config/$MIX_ENV.secret.exs`. In from source installations you should always make the changes to the user config and NEVER to the base config to avoid breakages and merge conflicts. So for production you change/add configuration to `config/prod.secret.exs`.
To add configuration to your config file, you can copy it from the base config. The latest version of it can be viewed [here](https://git.pleroma.social/pleroma/pleroma/blob/develop/config/config.exs). You can also use this file if you don't know how an option is supposed to be formatted.
## :shout
* `enabled` - Enables the backend Shoutbox chat feature. Defaults to `true`.
* `limit` - Shout character limit. Defaults to `5_000`
## :instance
* `name`: The instance’s name.
* `email`: Email used to reach an Administrator/Moderator of the instance.
* `notify_email`: Email used for notifications.
* `description`: The instance’s description, can be seen in nodeinfo and ``/api/v1/instance``.
* `short_description`: Shorter version of instance description, can be seen on ``/api/v1/instance``.
* `limit`: Posts character limit (CW/Subject included in the counter).
* `description_limit`: The character limit for image descriptions.
* `remote_limit`: Hard character limit beyond which remote posts will be dropped.
* `upload_limit`: File size limit of uploads (except for avatar, background, banner).
* `avatar_upload_limit`: File size limit of user’s profile avatars.
* `background_upload_limit`: File size limit of user’s profile backgrounds.
* `banner_upload_limit`: File size limit of user’s profile banners.
* `poll_limits`: A map with poll limits for **local** polls.
* `max_options`: Maximum number of options.
* `max_option_chars`: Maximum number of characters per option.
* `min_expiration`: Minimum expiration time (in seconds).
* `max_expiration`: Maximum expiration time (in seconds).
* `registrations_open`: Enable registrations for anyone, invitations can be enabled when false.
* `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`).
* `account_activation_required`: Require users to confirm their emails before signing in.
* `account_approval_required`: Require users to be manually approved by an admin before signing in.
* `federating`: Enable federation with other instances.
* `federation_incoming_replies_max_depth`: Max. depth of reply-to 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.
* `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.
* `allow_relay`: Permits remote instances to subscribe to all public posts of your instance. This may increase the visibility of your instance.
* `public`: Makes the client API in authenticated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network. Note that there is a dependent setting restricting or allowing unauthenticated access to specific resources, see `restrict_unauthenticated` for more details.
* `quarantined_instances`: ActivityPub instances where private (DMs, followers-only) activities will not be send.
* `rejected_instances`: ActivityPub instances to reject requests from if authorized_fetch_mode is enabled.
* `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML).
* `extended_nickname_format`: Set to `true` to use extended local nicknames format (allows underscores/dashes). This will break federation with
older software for theses nicknames.
* `max_pinned_statuses`: The maximum number of pinned statuses. `0` will disable the feature.
* `autofollowed_nicknames`: Set to nicknames of (local) users that every new user should automatically follow.
* `autofollowing_nicknames`: Set to nicknames of (local) users that automatically follows every newly registered user.
* `attachment_links`: Set to true to enable automatically adding attachment link text to statuses.
* `max_report_comment_size`: The maximum size of the report comment (Default: `1000`).
* `report_strip_status`: Strip associated statuses in reports to ids when closed/resolved, otherwise keep a copy.
* `safe_dm_mentions`: If set to true, 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. "@friend hey i really don't like @enemy"). Default: `false`.
* `healthcheck`: If set to true, system data will be shown on ``/api/v1/pleroma/healthcheck``.
* `remote_post_retention_days`: The default amount of days to retain remote posts when pruning the database.
* `user_bio_length`: A user bio maximum length (default: `5000`).
* `user_name_length`: A user name maximum length (default: `100`).
* `skip_thread_containment`: Skip filter out broken threads. The default is `false`.
* `limit_to_local_content`: Limit unauthenticated users to search for local statutes and users only. Possible values: `:unauthenticated`, `:all` and `false`. The default is `:unauthenticated`.
* `max_account_fields`: The maximum number of custom fields in the user profile (default: `10`).
* `max_remote_account_fields`: The maximum number of custom fields in the remote user profile (default: `20`).
* `account_field_name_length`: An account field name maximum length (default: `512`).
* `account_field_value_length`: An account field value maximum length (default: `2048`).
* `registration_reason_length`: Maximum registration reason length (default: `500`).
* `external_user_synchronization`: Enabling following/followers counters synchronization for external users.
* `cleanup_attachments`: Remove attachments along with statuses. Does not affect duplicate files and attachments without status. Enabling this will increase load to database when deleting statuses on larger instances.
* `show_reactions`: Let favourites and emoji reactions be viewed through the API (default: `true`).
* `password_reset_token_validity`: The time after which reset tokens aren't accepted anymore, in seconds (default: one day).
* `admin_privileges`: A list of privileges an admin has (e.g. delete messages, manage reports...)
* Possible values are:
* `:users_read`
* Allows admins to fetch users through the admin API.
* `:users_manage_invites`
* Allows admins to manage invites. This includes sending, resending, revoking and approving invites.
* `:users_manage_activation_state`
* Allows admins to activate and deactivate accounts. This also allows them to see deactivated users through the Mastodon API.
* `:users_manage_tags`
* Allows admins to set and remove tags for users. This can be useful in combination with MRF policies, such as `Pleroma.Web.ActivityPub.MRF.TagPolicy`.
* `:users_manage_credentials`
* Allows admins to trigger a password reset and set new credentials for an user.
* `:users_delete`
* Allows admins to delete accounts. Note that deleting an account is actually deactivating it and removing all data like posts, profile information, etc.
* `:messages_read`
* Allows admins to read messages through the admin API, including non-public posts and chats.
* `:messages_delete`
* Allows admins to delete messages from other users.
* `:instances_delete,`
* Allows admins to remove a whole remote instance from your instance. This will delete all users and messages from that remote instance.
* `:reports_manage_reports`
* Allows admins to see and manage reports.
* `:moderation_log_read,`
* Allows admins to read the entries in the moderation log.
* `:emoji_manage_emoji`
* Allows admins to manage custom emoji on the instance.
* `:statistics_read,`
* Allows admins to see some simple statistics about the instance.
* `moderator_privileges`: A list of privileges a moderator has (e.g. delete messages, manage reports...)
* Possible values are the same as for `admin_privileges`
## :features
* `improved_hashtag_timeline`: 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).
## Background migrations
* `populate_hashtags_table/sleep_interval_ms`: 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).
* `populate_hashtags_table/fault_rate_allowance`: Max rate of failed objects to actually processed objects in order to enable the feature (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).
## Welcome
* `direct_message`: - welcome message sent as a direct message.
* `enabled`: Enables the send a direct message to a newly registered user. Defaults to `false`.
* `sender_nickname`: The nickname of the local user that sends the welcome message.
* `message`: A message that will be send to a newly registered users as a direct message.
* `chat_message`: - welcome message sent as a chat message.
* `enabled`: Enables the send a chat message to a newly registered user. Defaults to `false`.
* `sender_nickname`: The nickname of the local user that sends the welcome message.
* `message`: A message that will be send to a newly registered users as a chat message.
* `email`: - welcome message sent as a email.
* `enabled`: Enables the send a welcome email to a newly registered user. Defaults to `false`.
* `sender`: The email address or tuple with `{nickname, email}` that will use as sender to the welcome email.
* `subject`: A subject of welcome email.
* `html`: A html that will be send to a newly registered users as a email.
* `text`: A text that will be send to a newly registered users as a email.
Example:
```elixir
config :pleroma, :welcome,
direct_message: [
enabled: true,
sender_nickname: "lain",
message: "Hi! Welcome on board!"
],
email: [
enabled: true,
sender: {"Pleroma App", "welcome@pleroma.app"},
subject: "Welcome to <%= instance_name %>",
html: "Welcome to <%= instance_name %>",
text: "Welcome to <%= instance_name %>"
]
```
## Message rewrite facility
### :mrf
* `policies`: Message Rewrite Policy, either one or a list. Here are the ones available by default:
* `Pleroma.Web.ActivityPub.MRF.NoOpPolicy`: Doesn’t modify activities (default).
* `Pleroma.Web.ActivityPub.MRF.DropPolicy`: Drops all activities. It generally doesn’t makes sense to use in production.
* `Pleroma.Web.ActivityPub.MRF.SimplePolicy`: Restrict the visibility of activities from certains instances (See [`:mrf_simple`](#mrf_simple)).
* `Pleroma.Web.ActivityPub.MRF.TagPolicy`: Applies policies to individual users based on tags, which can be set using pleroma-fe/admin-fe/any other app that supports Pleroma Admin API. For example it allows marking posts from individual users nsfw (sensitive).
* `Pleroma.Web.ActivityPub.MRF.SubchainPolicy`: Selectively runs other MRF policies when messages match (See [`:mrf_subchain`](#mrf_subchain)).
* `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`: Drops posts with non-public visibility settings (See [`:mrf_rejectnonpublic`](#mrf_rejectnonpublic)).
* `Pleroma.Web.ActivityPub.MRF.EnsureRePrepended`: Rewrites posts to ensure that replies to posts with subjects do not have an identical subject and instead begin with re:.
* `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links.
* `Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`: Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed.
* `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (See [`:mrf_mention`](#mrf_mention)).
* `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (See [`:mrf_vocabulary`](#mrf_vocabulary)).
* `Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy`: Rejects or delists posts based on their age when received. (See [`:mrf_object_age`](#mrf_object_age)).
* `Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy`: Sets a default expiration on all posts made by users of the local instance. Requires `Pleroma.Workers.PurgeExpiredActivity` to be enabled for processing the scheduled deletions.
* `Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy`: Makes all bot posts to disappear from public timelines.
* `Pleroma.Web.ActivityPub.MRF.FollowBotPolicy`: Automatically follows newly discovered users from the specified bot account. Local accounts, locked accounts, and users with "#nobot" in their bio are respected and excluded from being followed.
* `Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy`: Drops follow requests from followbots. Users can still allow bots to follow them by first following the bot.
* `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)).
* `Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent`: Forces every mentioned user to be reflected in the post content.
* `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Forces quote post URLs to be reflected in the message content inline.
* `Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicy`: Force a Link tag for posts quoting another post. (may break outgoing federation of quote posts with older Pleroma versions).
* `Pleroma.Web.ActivityPub.MRF.ForceMention`: Forces posts to include a mention of the author of parent post or the author of quoted post.
* `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
* `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
## Federation
### MRF policies
!!! note
Configuring MRF policies is not enough for them to take effect. You have to enable them by specifying their module in `policies` under [:mrf](#mrf) section.
#### :mrf_simple
* `media_removal`: List of instances to strip media attachments from and the reason for doing so.
* `media_nsfw`: List of instances to tag all media as NSFW (sensitive) from and the reason for doing so.
* `federated_timeline_removal`: List of instances to remove from the Federated Timeline (aka The Whole Known Network) and the reason for doing so.
* `reject`: List of instances to reject activities (except deletes) from and the reason for doing so.
* `accept`: List of instances to only accept activities (except deletes) from and the reason for doing so.
* `followers_only`: Force posts from the given instances to be visible by followers only and the reason for doing so.
* `report_removal`: List of instances to reject reports from and the reason for doing so.
* `avatar_removal`: List of instances to strip avatars from and the reason for doing so.
* `banner_removal`: List of instances to strip banners from and the reason for doing so.
* `reject_deletes`: List of instances to reject deletions from and the reason for doing so.
#### :mrf_subchain
This policy processes messages through an alternate pipeline when a given message matches certain criteria.
All criteria are configured as a map of regular expressions to lists of policy modules.
* `match_actor`: Matches a series of regular expressions against the actor field.
Example:
```elixir
config :pleroma, :mrf_subchain,
match_actor: %{
~r/https:\/\/example.com/s => [Pleroma.Web.ActivityPub.MRF.DropPolicy]
}
```
#### :mrf_rejectnonpublic
* `allow_followersonly`: whether to allow followers-only posts.
* `allow_direct`: whether to allow direct messages.
#### :mrf_hellthread
* `delist_threshold`: Number of mentioned users after which the message gets delisted (the message can still be seen, but it will not show up in public timelines and mentioned users won't get notifications about it). Set to 0 to disable.
* `reject_threshold`: Number of mentioned users after which the messaged gets rejected. Set to 0 to disable.
#### :mrf_keyword
* `reject`: A list of patterns which result in message being rejected, each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html).
* `federated_timeline_removal`: A list of patterns which result in message being removed from federated timelines (a.k.a unlisted), each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html).
* `replace`: A list of tuples containing `{pattern, replacement}`, `pattern` can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html).
#### :mrf_mention
* `actors`: A list of actors, for which to drop any posts mentioning.
#### :mrf_vocabulary
* `accept`: A list of ActivityStreams terms to accept. If empty, all supported messages are accepted.
* `reject`: A list of ActivityStreams terms to reject. If empty, no messages are rejected.
#### :mrf_user_allowlist
The keys in this section are the domain names that the policy should apply to.
Each key should be assigned a list of users that should be allowed through by
their ActivityPub ID.
An example:
```elixir
config :pleroma, :mrf_user_allowlist, %{
"example.org" => ["https://example.org/users/admin"]
}
```
#### :mrf_object_age
* `threshold`: Required time offset (in seconds) compared to your server clock of an incoming post before actions are taken.
e.g., A value of 900 results in any post with a timestamp older than 15 minutes will be acted upon.
* `actions`: A list of actions to apply to the post:
* `:delist` removes the post from public timelines
* `:strip_followers` removes followers from the ActivityPub recipient list, ensuring they won't be delivered to home timelines, additionally for followers-only it degrades to a direct message
* `:reject` rejects the message entirely
#### :mrf_steal_emoji
* `hosts`: List of hosts to steal emojis from
* `rejected_shortcodes`: Regex-list of shortcodes to reject
* `size_limit`: File size limit (in bytes), checked before an emoji is saved to the disk
#### :mrf_activity_expiration
* `days`: Default global expiration time for all local Create activities (in days)
#### :mrf_hashtag
* `sensitive`: List of hashtags to mark activities as sensitive (default: `nsfw`)
* `federated_timeline_removal`: List of hashtags to remove activities from the federated timeline (aka TWNK)
* `reject`: List of hashtags to reject activities from
Notes:
- The hashtags in the configuration do not have a leading `#`.
- This MRF Policy is always enabled, if you want to disable it you have to set empty lists
#### :mrf_follow_bot
* `follower_nickname`: The name of the bot account to use for following newly discovered users. Using `followbot` or similar is strongly suggested.
#### :mrf_emoji
* `remove_url`: A list of patterns which result in emoji whose URL matches being removed from the message. This will apply to statuses, emoji reactions, and user profiles. Each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html).
* `remove_shortcode`: A list of patterns which result in emoji whose shortcode matches being removed from the message. This will apply to statuses, emoji reactions, and user profiles. Each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html).
* `federated_timeline_removal_url`: A list of patterns which result in message with emojis whose URLs match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses. Each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html).
* `federated_timeline_removal_shortcode`: A list of patterns which result in message with emojis whose shortcodes match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses. Each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html).
#### :mrf_inline_quote
* `template`: The template to append to the post. `{url}` will be replaced with the actual link to the quoted post. Default: `<bdi>RT:</bdi> {url}`
#### :mrf_force_mention
* `mention_parent`: Whether to append mention of parent post author
* `mention_quoted`: Whether to append mention of parent quoted author
### :activitypub
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
* `outgoing_blocks`: Whether to federate blocks to other instances
* `blockers_visible`: Whether a user can see the posts of users who blocked them
* `deny_follow_blocked`: Whether to disallow following an account that has blocked the user in question
* `sign_object_fetches`: Sign object fetches with HTTP signatures
* `authorized_fetch_mode`: Require HTTP signatures for AP fetches
* `authorized_fetch_mode_exceptions`: List of IPs (CIDR format accepted) to exempt from HTTP Signatures requirement (for example to allow debugging, you shouldn't otherwise need this)
## Pleroma.User
* `restricted_nicknames`: List of nicknames users may not register with.
* `email_blacklist`: List of email domains users may not register with.
## Pleroma.ScheduledActivity
* `daily_user_limit`: the number of scheduled activities a user is allowed to create in a single day (Default: `25`)
* `total_user_limit`: the number of scheduled activities a user is allowed to create in total (Default: `300`)
* `enabled`: whether scheduled activities are sent to the job queue to be executed
### :frontend_configurations
This 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. You can find the documentation for `pleroma_fe` configuration into [Pleroma-FE configuration and customization for instance administrators](/frontend/CONFIGURATION/#options).
Frontends can access these settings at `/api/v1/pleroma/frontend_configurations`
To add your own configuration for PleromaFE, use it like this:
```elixir
config :pleroma, :frontend_configurations,
pleroma_fe: %{
theme: "pleroma-dark",
# ... see /priv/static/static/config.json for the available keys.
}
```
These settings **need to be complete**, they will override the defaults.
### :static_fe
Render profiles and posts using server-generated HTML that is viewable without using JavaScript.
Available options:
* `enabled` - Enables the rendering of static HTML. Defaults to `false`.
### :assets
This section configures assets to be used with various frontends. Currently the only option
relates to mascots on the mastodon frontend
* `mascots`: KeywordList of mascots, each element __MUST__ contain both a `url` and a
`mime_type` key.
* `default_mascot`: An element from `mascots` - This will be used as the default mascot
on MastoFE (default: `:pleroma_fox_tan`).
### :manifest
This section describe PWA manifest instance-specific values. Currently this option relate only for MastoFE.
* `icons`: Describe the icons of the app, this a list of maps describing icons in the same way as the
[spec](https://www.w3.org/TR/appmanifest/#imageresource-and-its-members) describes it.
Example:
```elixir
config :pleroma, :manifest,
icons: [
%{
src: "/static/logo.png"
},
%{
src: "/static/icon.png",
type: "image/png"
},
%{
src: "/static/icon.ico",
sizes: "72x72 96x96 128x128 256x256"
}
]
```
* `theme_color`: Describe the theme color of the app. (Example: `"#282c37"`, `"rebeccapurple"`).
* `background_color`: Describe the background color of the app. (Example: `"#191b22"`, `"aliceblue"`).
## :emoji
* `shortcode_globs`: Location of custom emoji files. `*` can be used as a wildcard. Example `["/emoji/custom/**/*.png"]`
* `pack_extensions`: A list of file extensions for emojis, when no emoji.txt for a pack is present. Example `[".png", ".gif"]`
* `groups`: Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the groupname and the value the location or array of locations. `*` can be used as a wildcard. Example `[Custom: ["/emoji/*.png", "/emoji/custom/*.png"]]`
* `default_manifest`: 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).
* `shared_pack_cache_seconds_per_file`: 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.
## :media_proxy
* `enabled`: Enables proxying of remote media to the instance’s proxy
* `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.
* `proxy_opts`: All options defined in `Pleroma.ReverseProxy` documentation, defaults to `[max_body_length: (25*1_048_576)]`.
* `whitelist`: List of hosts with scheme to bypass the mediaproxy (e.g. `https://example.com`)
* `invalidation`: options for remove media from cache after delete object:
* `enabled`: Enables purge cache
* `provider`: Which one of the [purge cache strategy](#purge-cache-strategy) to use.
## :media_preview_proxy
* `enabled`: Enables proxying of remote media preview to the instance’s proxy. Requires enabled media proxy (`media_proxy/enabled`).
* `thumbnail_max_width`: Max width of preview thumbnail for images (video preview always has original dimensions).
* `thumbnail_max_height`: Max height of preview thumbnail for images (video preview always has original dimensions).
* `image_quality`: Quality of the output. Ranges from 0 (min quality) to 100 (max quality).
* `min_content_length`: Min content length to perform preview, in bytes. If greater than 0, media smaller in size will be served as is, without thumbnailing.
### Purge cache strategy
#### Pleroma.Web.MediaProxy.Invalidation.Script
This strategy allow perform external shell script to purge cache.
Urls of attachments are passed to the script as arguments.
* `script_path`: Path to the external script.
* `url_format`: Set to `:htcacheclean` if using Apache's htcacheclean utility.
Example:
```elixir
config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Script,
script_path: "./installation/nginx-cache-purge.example"
```
#### Pleroma.Web.MediaProxy.Invalidation.Http
This strategy allow perform custom http request to purge cache.
* `method`: http method. default is `purge`
* `headers`: http headers.
* `options`: request options.
Example:
```elixir
config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Http,
method: :purge,
headers: [],
options: []
```
## Link previews
### Pleroma.Web.Metadata (provider)
* `providers`: a list of metadata providers to enable. Providers available:
* `Pleroma.Web.Metadata.Providers.OpenGraph`
* `Pleroma.Web.Metadata.Providers.TwitterCard`
* `unfurl_nsfw`: If set to `true` nsfw attachments will be shown in previews.
### :rich_media (consumer)
* `enabled`: if enabled the instance will parse metadata from attached links to generate link previews.
* `ignore_hosts`: list of hosts which will be ignored by the metadata parser. For example `["accounts.google.com", "xss.website"]`, defaults to `[]`.
* `ignore_tld`: list TLDs (top-level domains) which will ignore for parse metadata. default is ["local", "localdomain", "lan"].
* `parsers`: list of Rich Media parsers.
* `timeout`: Amount of milliseconds after which the HTTP request is forcibly terminated.
## HTTP server
### Pleroma.Web.Endpoint
!!! note
`Phoenix` endpoint configuration, all configuration options can be viewed [here](https://hexdocs.pm/phoenix/Phoenix.Endpoint.html#module-dynamic-configuration), only common options are listed here.
* `http` - a list containing http protocol configuration, all configuration options can be viewed [here](https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html#module-options), only common options are listed here. For deployment using docker, you need to set this to `[ip: {0,0,0,0}, port: 4000]` to make pleroma accessible from other containers (such as your nginx server).
- `ip` - a tuple consisting of 4 integers
- `port`
* `url` - a list containing the configuration for generating urls, accepts
- `host` - the host without the scheme and a post (e.g `example.com`, not `https://example.com:2020`)
- `scheme` - e.g `http`, `https`
- `port`
- `path`
* `extra_cookie_attrs` - a list of `Key=Value` strings to be added as non-standard cookie attributes. Defaults to `["SameSite=Lax"]`. See the [SameSite article](https://www.owasp.org/index.php/SameSite) on OWASP for more info.
Example:
```elixir
config :pleroma, Pleroma.Web.Endpoint,
url: [host: "example.com", port: 2020, scheme: "https"],
http: [
port: 8080,
ip: {127, 0, 0, 1}
]
```
This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls starting with `https://example.com:2020`
### :http_security
* ``enabled``: Whether the managed content security policy is enabled.
* ``sts``: Whether to additionally send a `Strict-Transport-Security` header.
* ``sts_max_age``: The maximum age for the `Strict-Transport-Security` header if sent.
* ``ct_max_age``: The maximum age for the `Expect-CT` header if sent.
* ``referrer_policy``: The referrer policy to use, either `"same-origin"` or `"no-referrer"`.
* ``report_uri``: Adds the specified url to `report-uri` and `report-to` group in CSP header.
* `allow_unsafe_eval`: Adds `wasm-unsafe-eval` to the CSP header. Needed for some non-essential frontend features like Flash emulation.
### Pleroma.Web.Plugs.RemoteIp
!!! warning
If your instance is not behind at least one reverse proxy, you should not enable this plug.
`Pleroma.Web.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
Available options:
* `enabled` - Enable/disable the plug. Defaults to `false`.
* `headers` - A list of strings naming the HTTP headers to use when deriving the true client IP address. Defaults to `["x-forwarded-for"]`.
* `proxies` - 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.
* `reserved` - 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"]`.
### :rate_limit
!!! note
If your instance is behind a reverse proxy ensure [`Pleroma.Web.Plugs.RemoteIp`](#pleroma-plugs-remoteip) is enabled (it is enabled by default).
A keyword list of rate limiters where a key is a limiter name and value is the limiter configuration. The basic configuration is a tuple where:
* The first element: `scale` (Integer). The time scale in milliseconds.
* The second element: `limit` (Integer). How many requests to limit in the time scale provided.
It is also possible to have different limits for unauthenticated and authenticated users: the keyword value must be a list of two tuples where the first one is a config for unauthenticated users and the second one is for authenticated.
For example:
```elixir
config :pleroma, :rate_limit,
authentication: {60_000, 15},
search: [{1000, 10}, {1000, 30}]
```
Means that:
1. In 60 seconds, 15 authentication attempts can be performed from the same IP address.
2. In 1 second, 10 search requests can be performed from the same IP address by unauthenticated users, while authenticated users can perform 30 search requests per second.
Supported rate limiters:
* `:search` - Account/Status search.
* `:timeline` - Timeline requests (each timeline has it's own limiter).
* `:app_account_creation` - Account registration from the API.
* `:relations_actions` - Following/Unfollowing in general.
* `:relation_id_action` - Following/Unfollowing for a specific user.
* `:statuses_actions` - Status actions such as: (un)repeating, (un)favouriting, creating, deleting.
* `:status_id_action` - (un)Repeating/(un)Favouriting a particular status.
* `:authentication` - Authentication actions, i.e getting an OAuth token.
* `:password_reset` - Requesting password reset emails.
* `:account_confirmation_resend` - Requesting resending account confirmation emails.
* `:ap_routes` - Requesting statuses via ActivityPub.
### :web_cache_ttl
The expiration time for the web responses cache. Values should be in milliseconds or `nil` to disable expiration.
Available caches:
* `:activity_pub` - activity pub routes (except question activities). Defaults to `nil` (no expiration).
* `:activity_pub_question` - activity pub routes (question activities). Defaults to `30_000` (30 seconds).
## HTTP client
### :http
* `proxy_url`: an upstream proxy to fetch posts and/or media with, (default: `nil`)
* `send_user_agent`: should we include a user agent with HTTP requests? (default: `true`)
* `user_agent`: what user agent should we use? (default: `:default`), must be string or `:default`
* `adapter`: array of adapter options
### :hackney_pools
Advanced. Tweaks Hackney (http client) connections pools.
There's three pools used:
* `:federation` for the federation jobs.
You may want this pool max_connections to be at least equal to the number of federator jobs + retry queue jobs.
* `:media` for rich media, media proxy
* `:upload` for uploaded media (if using a remote uploader and `proxy_remote: true`)
For each pool, the options are:
* `max_connections` - how much connections a pool can hold
* `timeout` - retention duration for connections
### :connections_pool
*For `gun` adapter*
Settings for HTTP connection pool.
* `:connection_acquisition_wait` - Timeout to acquire a connection from pool.The total max time is this value multiplied by the number of retries.
* `connection_acquisition_retries` - Number of attempts to acquire the connection from the pool if it is overloaded. Each attempt is timed `:connection_acquisition_wait` apart.
* `:max_connections` - Maximum number of connections in the pool.
* `:connect_timeout` - Timeout to connect to the host.
* `:reclaim_multiplier` - Multiplied by `:max_connections` this will be the maximum number of idle connections that will be reclaimed in case the pool is overloaded.
### :pools
*For `gun` adapter*
Settings for request pools. These pools are limited on top of `:connections_pool`.
There are four pools used:
* `:federation` for the federation jobs. You may want this pool's max_connections to be at least equal to the number of federator jobs + retry queue jobs.
* `:media` - for rich media, media proxy.
* `:upload` - for proxying media when a remote uploader is used and `proxy_remote: true`.
* `:default` - for other requests.
For each pool, the options are:
* `:size` - limit to how much requests can be concurrently executed.
* `:recv_timeout` - timeout while `gun` will wait for response
* `:max_waiting` - limit to how much requests can be waiting for others to finish, after this is reached, subsequent requests will be dropped.
## Captcha
### Pleroma.Captcha
* `enabled`: Whether the captcha should be shown on registration.
* `method`: The method/service to use for captcha.
* `seconds_valid`: The time in seconds for which the captcha is valid.
### Captcha providers
#### Pleroma.Captcha.Native
A built-in captcha provider. Enabled by default.
#### Pleroma.Captcha.Kocaptcha
Kocaptcha is a very simple captcha service with a single API endpoint,
the source code is here: [kocaptcha](https://github.com/koto-bank/kocaptcha). The default endpoint
`https://captcha.kotobank.ch` is hosted by the developer.
* `endpoint`: the Kocaptcha endpoint to use.
## Uploads
### Pleroma.Upload
* `uploader`: Which one of the [uploaders](#uploaders) to use.
* `filters`: List of [upload filters](#upload-filters) to use.
* `link_name`: When enabled Pleroma will add a `name` parameter to the url of the upload, for example `https://instance.tld/media/corndog.png?name=corndog.png`. This is needed to provide the correct filename in Content-Disposition headers when using filters like `Pleroma.Upload.Filter.Dedupe`
* `base_url`: The base URL to access a user-uploaded file. Useful when you want to host the media files via another domain or are using a 3rd party S3 provider.
* `proxy_remote`: If you're using a remote uploader, Pleroma will proxy media requests instead of redirecting to it.
* `proxy_opts`: Proxy options, see `Pleroma.ReverseProxy` documentation.
* `filename_display_max_length`: Set max length of a filename to display. 0 = no limit. Default: 30.
* `default_description`: Sets which default description an image has if none is set explicitly. Options: nil (default) - Don't set a default, :filename - use the filename of the file, a string (e.g. "attachment") - Use this string
!!! warning
`strip_exif` has been replaced by `Pleroma.Upload.Filter.Mogrify`.
### Uploaders
#### Pleroma.Uploaders.Local
* `uploads`: Which directory to store the user-uploads in, relative to pleroma’s working directory.
#### Pleroma.Uploaders.S3
Don't forget to configure [Ex AWS S3](#ex-aws-s3-settings)
* `bucket`: S3 bucket name.
* `bucket_namespace`: S3 bucket namespace.
* `truncated_namespace`: If you use S3 compatible service such as Digital Ocean Spaces or CDN, set folder name or "" etc.
* `streaming_enabled`: 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.
#### Ex AWS S3 settings
* `access_key_id`: Access key ID
* `secret_access_key`: Secret access key
* `host`: S3 host
Example:
```elixir
config :ex_aws, :s3,
access_key_id: "xxxxxxxxxx",
secret_access_key: "yyyyyyyyyy",
host: "s3.eu-central-1.amazonaws.com"
```
#### Pleroma.Uploaders.IPFS
* `post_gateway_url`: URL with port of POST Gateway (unauthenticated)
* `get_gateway_url`: URL of public GET Gateway
Example:
```elixir
config :pleroma, Pleroma.Uploaders.IPFS,
post_gateway_url: "http://localhost:5001",
get_gateway_url: "http://{CID}.ipfs.mydomain.com"
```
### Upload filters
#### Pleroma.Upload.Filter.AnonymizeFilename
This filter replaces the filename (not the path) of an upload. For complete obfuscation, add
`Pleroma.Upload.Filter.Dedupe` before AnonymizeFilename.
* `text`: Text to replace filenames in links. If empty, `{random}.extension` will be used. You can get the original filename extension by using `{extension}`, for example `custom-file-name.{extension}`.
#### Pleroma.Upload.Filter.Dedupe
No specific configuration.
#### Pleroma.Upload.Filter.Exiftool.StripLocation
-This filter only strips the GPS and location metadata with Exiftool leaving color profiles and attributes intact.
+This filter only strips the GPS and location metadata leaving color profiles and attributes intact.
No specific configuration.
#### Pleroma.Upload.Filter.Exiftool.ReadDescription
-This filter reads the ImageDescription and iptc:Caption-Abstract fields with Exiftool so clients can prefill the media description field.
+This filter reads the ImageDescription and iptc:Caption-Abstract fields so clients can prefill the media description field.
No specific configuration.
#### Pleroma.Upload.Filter.OnlyMedia
This filter rejects uploads that are not identified with Content-Type matching audio/\*, image/\*, or video/\*
No specific configuration.
#### Pleroma.Upload.Filter.Mogrify
* `args`: List of actions for the `mogrify` command like `"strip"` or `["strip", "auto-orient", {"implode", "1"}]`.
## Email
### Pleroma.Emails.Mailer
* `adapter`: one of the mail adapters listed in [Swoosh readme](https://github.com/swoosh/swoosh#adapters), or `Swoosh.Adapters.Local` for in-memory mailbox.
* `api_key` / `password` and / or other adapter-specific settings, per the above documentation.
* `enabled`: Allows enable/disable send emails. Default: `false`.
An example for Sendgrid adapter:
```elixir
config :pleroma, Pleroma.Emails.Mailer,
enabled: true,
adapter: Swoosh.Adapters.Sendgrid,
api_key: "YOUR_API_KEY"
```
An example for SMTP adapter:
```elixir
config :pleroma, Pleroma.Emails.Mailer,
enabled: true,
adapter: Swoosh.Adapters.Mua,
relay: "smtp.gmail.com",
auth: [username: "YOUR_USERNAME@gmail.com", password: "YOUR_SMTP_PASSWORD"],
port: 465,
protocol: :ssl
```
An example for Mua adapter:
```elixir
config :pleroma, Pleroma.Emails.Mailer,
enabled: true,
adapter: Swoosh.Adapters.Mua,
relay: "mail.example.com",
port: 465,
auth: [
username: "YOUR_USERNAME@domain.tld",
password: "YOUR_SMTP_PASSWORD"
],
protocol: :ssl
```
### :email_notifications
Email notifications settings.
- digest - emails of "what you've missed" for users who have been
inactive for a while.
- active: globally enable or disable digest emails
- schedule: When to send digest email, in [crontab format](https://en.wikipedia.org/wiki/Cron).
"0 0 * * 0" is the default, meaning "once a week at midnight on Sunday morning"
- interval: Minimum interval between digest emails to one user
- inactivity_threshold: Minimum user inactivity threshold
### Pleroma.Emails.UserEmail
- `:logo` - a path to a custom logo. Set it to `nil` to use the default Pleroma logo.
- `:styling` - a map with color settings for email templates.
### Pleroma.Emails.NewUsersDigestEmail
- `:enabled` - a boolean, enables new users admin digest email when `true`. Defaults to `false`.
## Background jobs
### Oban
[Oban](https://github.com/sorentwo/oban) asynchronous job processor configuration.
Configuration options described in [Oban readme](https://github.com/sorentwo/oban#usage):
* `repo` - app's Ecto repo (`Pleroma.Repo`)
* `log` - logs verbosity
* `queues` - job queues (see below)
* `crontab` - periodic jobs, see [`Oban.Cron`](#obancron)
Pleroma has the following queues:
* `activity_expiration` - Activity expiration
* `federator_outgoing` - Outgoing federation
* `federator_incoming` - Incoming federation
* `mailer` - Email sender, see [`Pleroma.Emails.Mailer`](#pleromaemailsmailer)
* `transmogrifier` - Transmogrifier
* `web_push` - Web push notifications
* `scheduled_activities` - Scheduled activities, see [`Pleroma.ScheduledActivity`](#pleromascheduledactivity)
#### Oban.Cron
Pleroma has these periodic job workers:
* `Pleroma.Workers.Cron.DigestEmailsWorker` - digest emails for users with new mentions and follows
* `Pleroma.Workers.Cron.NewUsersDigestWorker` - digest emails for admins with new registrations
```elixir
config :pleroma, Oban,
repo: Pleroma.Repo,
verbose: false,
prune: {:maxlen, 1500},
queues: [
federator_incoming: 50,
federator_outgoing: 50
],
crontab: [
{"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
{"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}
]
```
This config contains two queues: `federator_incoming` and `federator_outgoing`. Both have the number of max concurrent jobs set to `50`.
#### Migrating `pleroma_job_queue` settings
`config :pleroma_job_queue, :queues` is replaced by `config :pleroma, Oban, :queues` and uses the same format (keys are queues' names, values are max concurrent jobs numbers).
### :workers
Includes custom worker options not interpretable directly by `Oban`.
* `retries` — keyword lists where keys are `Oban` queues (see above) and values are numbers of max attempts for failed jobs.
Example:
```elixir
config :pleroma, :workers,
retries: [
federator_incoming: 5,
federator_outgoing: 5
]
```
#### Migrating `Pleroma.Web.Federator.RetryQueue` settings
* `max_retries` is replaced with `config :pleroma, :workers, retries: [federator_outgoing: 5]`
* `enabled: false` corresponds to `config :pleroma, :workers, retries: [federator_outgoing: 1]`
* deprecated options: `max_jobs`, `initial_timeout`
## :web_push_encryption, :vapid_details
Web Push Notifications configuration. You can use the mix task `mix web_push.gen.keypair` to generate it.
* ``subject``: a mailto link for the administrative contact. It’s best if this email is not a personal email address, but rather a group email so that if a person leaves an organization, is unavailable for an extended period, or otherwise can’t respond, someone else on the list can.
* ``public_key``: VAPID public key
* ``private_key``: VAPID private key
## :logger
* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog
An example to enable ONLY ExSyslogger (f/ex in ``prod.secret.exs``) with info and debug suppressed:
```elixir
config :logger,
backends: [{ExSyslogger, :ex_syslogger}]
config :logger, :ex_syslogger,
level: :warning
```
Another example, keeping console output and adding the pid to syslog output:
```elixir
config :logger,
backends: [:console, {ExSyslogger, :ex_syslogger}]
config :logger, :ex_syslogger,
level: :warning,
option: [:pid, :ndelay]
```
See: [logger’s documentation](https://hexdocs.pm/logger/Logger.html) and [ex_syslogger’s documentation](https://hexdocs.pm/ex_syslogger/)
An example of logging info to local syslog, but debug to console:
```elixir
config :logger,
backends: [ {ExSyslogger, :ex_syslogger}, :console ],
level: :info
config :logger, :ex_syslogger,
level: :info,
ident: "pleroma",
format: "$metadata[$level] $message"
config :logger, :console,
level: :debug,
format: "\n$time $metadata[$level] $message\n",
metadata: [:request_id]
```
## Database options
### RUM indexing for full text search
* `rum_enabled`: If RUM indexes should be used. Defaults to `false`.
RUM indexes are an alternative indexing scheme that is not included in PostgreSQL by default. While they may eventually be mainlined, for now they have to be installed as a PostgreSQL extension from [https://github.com/postgrespro/rum](https://github.com/postgrespro/rum).
Their advantage over the standard GIN indexes is that they allow efficient ordering of search results by timestamp, which makes search queries a lot faster on larger servers, by one or two orders of magnitude. They take up around 3-4 times as much space as GIN indexes.
To enable them, both the `rum_enabled` flag has to be set and the following special migration has to be run:
* Source install:
- Stop Pleroma
- `mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/`
* OTP install:
- Stop Pleroma
- `pleroma_ctl migrate --migrations-path priv/repo/optional_migrations/rum_indexing/`
This will probably take a long time.
!!! note
It is recommended to `VACUUM FULL` the objects table after the migration has completed, to do that run:
```
# sudo -Hu postgres vacuumdb --full --analyze -t objects <pleroma DB name>
```
Now you can start Pleroma back up.
## Alternative client protocols
### BBS / SSH access
This feature has been removed from Pleroma core.
However, a client has been made and is available at https://git.pleroma.social/Duponin/sshocial.
### :gopher
* `enabled`: Enables the gopher interface
* `ip`: IP address to bind to
* `port`: Port to bind to
* `dstport`: Port advertised in urls (optional, defaults to `port`)
## Authentication
### :admin_token
Allows to set a token that can be used to authenticate with the admin api without using an actual user by giving it as the `admin_token` parameter or `x-admin-token` HTTP header. Example:
```elixir
config :pleroma, :admin_token, "somerandomtoken"
```
You can then do
```shell
curl "http://localhost:4000/api/v1/pleroma/admin/users/invites?admin_token=somerandomtoken"
```
or
```shell
curl -H "X-Admin-Token: somerandomtoken" "http://localhost:4000/api/v1/pleroma/admin/users/invites"
```
Warning: it's discouraged to use this feature because of the associated security risk: static / rarely changed instance-wide token is much weaker compared to email-password pair of a real admin user; consider using HTTP Basic Auth or OAuth-based authentication instead.
### :auth
Authentication / authorization settings.
* `auth_template`: authentication form template. By default it's `show.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/show.html.eex`.
* `oauth_consumer_template`: 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`.
* `oauth_consumer_strategies`: 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>`).
### Pleroma.Web.Auth.Authenticator
* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator.
* `Pleroma.Web.Auth.LDAPAuthenticator`: LDAP authentication.
### :ldap
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 an 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.
* `enabled`: enables LDAP authentication
* `host`: LDAP server hostname
* `port`: LDAP port, e.g. 389 or 636
* `ssl`: true to use implicit SSL/TLS, usually port 636
* `sslopts`: additional SSL options
* `tls`: true to use explicit TLS (STARTTLS), usually port 389
* `tlsopts`: additional TLS options
* `base`: LDAP base, e.g. "dc=example,dc=com"
* `uid`: LDAP attribute name to authenticate the user, e.g. when "cn", the filter will be "cn=username,base"
* `cacertfile`: Path to alternate CA root certificates file
Note, if your LDAP server is an Active Directory server the correct value is commonly `uid: "cn"`, but if you use an
OpenLDAP server the value may be `uid: "uid"`.
### :oauth2 (Pleroma as OAuth 2.0 provider settings)
OAuth 2.0 provider settings:
* `token_expires_in` - The lifetime in seconds of the access token.
* `issue_new_refresh_token` - Keeps old refresh token or generate new refresh token when to obtain an access token.
* `clean_expired_tokens` - Enable a background job to clean expired oauth tokens. Defaults to `false`.
OAuth 2.0 provider and related endpoints:
* `POST /api/v1/apps` creates client app basing on provided params.
* `GET/POST /oauth/authorize` renders/submits authorization form.
* `POST /oauth/token` creates/renews OAuth token.
* `POST /oauth/revoke` revokes provided OAuth token.
* `GET /api/v1/accounts/verify_credentials` (with proper `Authorization` header or `access_token` URI param) returns user info on requester (with `acct` field containing local nickname and `fqn` field containing fully-qualified nickname which could generally be used as email stub for OAuth software that demands email field in identity endpoint response, like Peertube).
### OAuth consumer mode
OAuth consumer mode allows sign in / sign up via external OAuth providers (e.g. Twitter, Facebook, Google, Microsoft, etc.).
Implementation is based on Ueberauth; see the list of [available strategies](https://github.com/ueberauth/ueberauth/wiki/List-of-Strategies).
!!! note
Each strategy is shipped as a separate dependency; in order to get the strategies, run `OAUTH_CONSUMER_STRATEGIES="..." mix deps.get`, e.g. `OAUTH_CONSUMER_STRATEGIES="twitter facebook google microsoft" mix deps.get`. The server should also be started with `OAUTH_CONSUMER_STRATEGIES="..." mix phx.server` in case you enable any strategies.
!!! note
Each strategy requires separate setup (on external provider side and Pleroma side). Below are the guidelines on setting up most popular strategies.
!!! note
Make sure that `"SameSite=Lax"` is set in `extra_cookie_attrs` when you have this feature enabled. OAuth consumer mode will not work with `"SameSite=Strict"`
* For Twitter, [register an app](https://developer.twitter.com/en/apps), configure callback URL to https://<your_host>/oauth/twitter/callback
* For Facebook, [register an app](https://developers.facebook.com/apps), configure callback URL to https://<your_host>/oauth/facebook/callback, enable Facebook Login service at https://developers.facebook.com/apps/<app_id>/fb-login/settings/
* For Google, [register an app](https://console.developers.google.com), configure callback URL to https://<your_host>/oauth/google/callback
* For Microsoft, [register an app](https://portal.azure.com), configure callback URL to https://<your_host>/oauth/microsoft/callback
Once the app is configured on external OAuth provider side, add app's credentials and strategy-specific settings (if any — e.g. see Microsoft below) to `config/prod.secret.exs`,
per strategy's documentation (e.g. [ueberauth_twitter](https://github.com/ueberauth/ueberauth_twitter)). Example config basing on environment variables:
```elixir
# Twitter
config :ueberauth, Ueberauth.Strategy.Twitter.OAuth,
consumer_key: System.get_env("TWITTER_CONSUMER_KEY"),
consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET")
# Facebook
config :ueberauth, Ueberauth.Strategy.Facebook.OAuth,
client_id: System.get_env("FACEBOOK_APP_ID"),
client_secret: System.get_env("FACEBOOK_APP_SECRET"),
redirect_uri: System.get_env("FACEBOOK_REDIRECT_URI")
# Google
config :ueberauth, Ueberauth.Strategy.Google.OAuth,
client_id: System.get_env("GOOGLE_CLIENT_ID"),
client_secret: System.get_env("GOOGLE_CLIENT_SECRET"),
redirect_uri: System.get_env("GOOGLE_REDIRECT_URI")
# Microsoft
config :ueberauth, Ueberauth.Strategy.Microsoft.OAuth,
client_id: System.get_env("MICROSOFT_CLIENT_ID"),
client_secret: System.get_env("MICROSOFT_CLIENT_SECRET")
config :ueberauth, Ueberauth,
providers: [
microsoft: {Ueberauth.Strategy.Microsoft, [callback_params: []]}
]
# Keycloak
# Note: make sure to add `keycloak:ueberauth_keycloak_strategy` entry to `OAUTH_CONSUMER_STRATEGIES` environment variable
keycloak_url = "https://publicly-reachable-keycloak-instance.org:8080"
config :ueberauth, Ueberauth.Strategy.Keycloak.OAuth,
client_id: System.get_env("KEYCLOAK_CLIENT_ID"),
client_secret: System.get_env("KEYCLOAK_CLIENT_SECRET"),
site: keycloak_url,
authorize_url: "#{keycloak_url}/auth/realms/master/protocol/openid-connect/auth",
token_url: "#{keycloak_url}/auth/realms/master/protocol/openid-connect/token",
userinfo_url: "#{keycloak_url}/auth/realms/master/protocol/openid-connect/userinfo",
token_method: :post
config :ueberauth, Ueberauth,
providers: [
keycloak: {Ueberauth.Strategy.Keycloak, [uid_field: :email]}
]
```
## Link parsing
### :uri_schemes
* `valid_schemes`: List of the scheme part that is considered valid to be an URL.
### Pleroma.Formatter
Configuration for Pleroma's link formatter which parses mentions, hashtags, and URLs.
* `class` - specify the class to be added to the generated link (default: `false`)
* `rel` - specify the rel attribute (default: `ugc`)
* `new_window` - adds `target="_blank"` attribute (default: `false`)
* `truncate` - Set to a number to truncate URLs longer then the number. Truncated URLs will end in `...` (default: `false`)
* `strip_prefix` - Strip the scheme prefix (default: `false`)
* `extra` - link URLs with rarely used schemes (magnet, ipfs, irc, etc.) (default: `true`)
* `validate_tld` - 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) (default: `:no_scheme`)
Example:
```elixir
config :pleroma, Pleroma.Formatter,
class: false,
rel: "ugc",
new_window: false,
truncate: false,
strip_prefix: false,
extra: true,
validate_tld: :no_scheme
```
## Custom Runtime Modules (`:modules`)
* `runtime_dir`: A path to custom Elixir modules (such as MRF policies).
## :configurable_from_database
Boolean, enables/disables in-database configuration. Read [Transferring the config to/from the database](../administration/CLI_tasks/config.md) for more information.
## :database_config_whitelist
List of valid configuration sections which are allowed to be configured from the
database. Settings stored in the database before the whitelist is configured are
still applied, so it is suggested to only use the whitelist on instances that
have not migrated the config to the database.
Example:
```elixir
config :pleroma, :database_config_whitelist, [
{:pleroma, :instance},
{:pleroma, Pleroma.Web.Metadata},
{:auto_linker}
]
```
### Multi-factor authentication - :two_factor_authentication
* `totp` - a list containing TOTP configuration
- `digits` - Determines the length of a one-time pass-code in characters. Defaults to 6 characters.
- `period` - a period for which the TOTP code will be valid in seconds. Defaults to 30 seconds.
* `backup_codes` - a list containing backup codes configuration
- `number` - number of backup codes to generate.
- `length` - backup code length. Defaults to 16 characters.
## Restrict entities access for unauthenticated users
### :restrict_unauthenticated
Restrict access for unauthenticated users to timelines (public and federated), user profiles and statuses.
* `timelines`: public and federated timelines
* `local`: public timeline
* `federated`: federated timeline (includes public timeline)
* `profiles`: user profiles
* `local`
* `remote`
* `activities`: statuses
* `local`
* `remote`
Note: when `:instance, :public` is set to `false`, all `:restrict_unauthenticated` items be effectively set to `true` by default. If you'd like to allow unauthenticated access to specific API endpoints on a private instance, please explicitly set `:restrict_unauthenticated` to non-default value in `config/prod.secret.exs`.
Note: setting `restrict_unauthenticated/timelines/local` to `true` has no practical sense if `restrict_unauthenticated/timelines/federated` is set to `false` (since local public activities will still be delivered to unauthenticated users as part of federated timeline).
## Pleroma.Web.ApiSpec.CastAndValidate
* `:strict` a boolean, enables strict input validation (useful in development, not recommended in production). Defaults to `false`.
## :instances_favicons
Control favicons for instances.
* `enabled`: Allow/disallow displaying and getting instances favicons
## Pleroma.User.Backup
!!! note
Requires enabled email
* `:purge_after_days` an integer, remove backup achieves after N days.
* `:limit_days` an integer, limit user to export not more often than once per N days.
* `:dir` a string with a path to backup temporary directory or `nil` to let Pleroma choose temporary directory in the following order:
1. the directory named by the TMPDIR environment variable
2. the directory named by the TEMP environment variable
3. the directory named by the TMP environment variable
4. C:\TMP on Windows or /tmp on Unix-like operating systems
5. as a last resort, the current working directory
* `:timeout` an integer representing seconds
## Frontend management
Frontends in Pleroma are swappable - you can specify which one to use here.
You can set a frontends for the key `primary` and `admin` and the options of `name` and `ref`. This will then make Pleroma serve the frontend from a folder constructed by concatenating the instance static path, `frontends` and the name and ref.
The key `primary` refers to the frontend that will be served by default for general requests. The key `admin` refers to the frontend that will be served at the `/pleroma/admin` path.
If you don't set anything here, the bundled frontends will be used.
Example:
```
config :pleroma, :frontends,
primary: %{
"name" => "pleroma",
"ref" => "stable"
},
admin: %{
"name" => "admin",
"ref" => "develop"
}
```
This would serve the frontend from the the folder at `$instance_static/frontends/pleroma/stable`. You have to copy the frontend into this folder yourself. You can choose the name and ref any way you like, but they will be used by mix tasks to automate installation in the future, the name referring to the project and the ref referring to a commit.
## Ephemeral activities (Pleroma.Workers.PurgeExpiredActivity)
Settings to enable and configure expiration for ephemeral activities
* `:enabled` - enables ephemeral activities creation
* `:min_lifetime` - minimum lifetime for ephemeral activities (in seconds). Default: 10 minutes.
## ConcurrentLimiter
Settings to restrict concurrently running jobs. Jobs which can be configured:
* `Pleroma.Web.RichMedia.Helpers` - generating link previews of URLs in activities
* `Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy` - warming remote media cache via MediaProxyWarmingPolicy
Each job has these settings:
* `:max_running` - max concurrently runnings jobs
* `:max_waiting` - max waiting jobs
diff --git a/docs/installation/alpine_linux_en.md b/docs/installation/alpine_linux_en.md
index 7154bca48..777a94dfc 100644
--- a/docs/installation/alpine_linux_en.md
+++ b/docs/installation/alpine_linux_en.md
@@ -1,231 +1,231 @@
# Installing on Alpine Linux
{! backend/installation/otp_vs_from_source_source.include !}
## Installation
This guide is a step-by-step installation guide for Alpine Linux. The instructions were verified against Alpine v3.10 standard image. You might miss additional dependencies if you use `netboot` instead.
It assumes that you have administrative rights, either as root or a user with [sudo permissions](https://www.linode.com/docs/tools-reference/custom-kernels-distros/install-alpine-linux-on-your-linode/#configuration). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su -l <username> -s $SHELL -c 'command'` instead.
{! backend/installation/generic_dependencies.include !}
### Prepare the system
* The community repository must be enabled in `/etc/apk/repositories`. Depending on which version and mirror you use this looks like `http://alpine.42.fr/v3.10/community`. If you autogenerated the mirror during installation:
```shell
awk 'NR==2' /etc/apk/repositories | sed 's/main/community/' | tee -a /etc/apk/repositories
```
* Then update the system, if not already done:
```shell
sudo apk update
sudo apk upgrade
```
* Install some tools, which are needed later:
```shell
sudo apk add git build-base cmake file-dev
```
### Install Elixir and Erlang
* Install Erlang and Elixir:
```shell
sudo apk add erlang erlang-runtime-tools erlang-xmerl elixir
```
* Install `erlang-eldap` if you want to enable ldap authenticator
```shell
sudo apk add erlang-eldap
```
### Install PostgreSQL
* Install Postgresql server:
```shell
sudo apk add postgresql postgresql-contrib
```
* Initialize database:
```shell
sudo /etc/init.d/postgresql start
```
* Enable and start postgresql server:
```shell
sudo rc-update add postgresql
```
### Install media / graphics packages (optional, see [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md))
```shell
-sudo apk add ffmpeg imagemagick exiftool
+sudo apk add ffmpeg imagemagick
```
### Install PleromaBE
* Add a new system user for the Pleroma service:
```shell
sudo addgroup pleroma
sudo adduser -S -s /bin/false -h /opt/pleroma -H -G pleroma pleroma
```
**Note**: To execute a single command as the Pleroma system user, use `sudo -Hu pleroma command`. You can also switch to a shell by using `sudo -Hu pleroma $SHELL`. If you don’t have and want `sudo` on your system, you can use `su` as root user (UID 0) for a single command by using `su -l pleroma -s $SHELL -c 'command'` and `su -l pleroma -s $SHELL` for starting a shell.
* Git clone the PleromaBE repository and make the Pleroma user the owner of the directory:
```shell
sudo mkdir -p /opt/pleroma
sudo chown -R pleroma:pleroma /opt/pleroma
sudo -Hu pleroma git clone -b stable https://git.pleroma.social/pleroma/pleroma /opt/pleroma
```
* Change to the new directory:
```shell
cd /opt/pleroma
```
* Install the dependencies for Pleroma and answer with `yes` if it asks you to install `Hex`:
```shell
sudo -Hu pleroma mix deps.get
```
* Generate the configuration: `sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen`
* Answer with `yes` if it asks you to install `rebar3`.
* This may take some time, because parts of pleroma get compiled first.
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
* Check the configuration and if all looks right, rename it, so Pleroma will load it (`prod.secret.exs` for productive instance, `dev.secret.exs` for development instances):
```shell
sudo -Hu pleroma mv config/{generated_config.exs,prod.secret.exs}
```
* The previous command creates also the file `config/setup_db.psql`, with which you can create the database:
```shell
sudo -Hu postgres psql -f config/setup_db.psql
```
* Now run the database migration:
```shell
sudo -Hu pleroma MIX_ENV=prod mix ecto.migrate
```
* Now you can start Pleroma already
```shell
sudo -Hu pleroma MIX_ENV=prod mix phx.server
```
### Finalize installation
If you want to open your newly installed instance to the world, you should run nginx or some other webserver/proxy in front of Pleroma and you should consider to create an OpenRC service file for Pleroma.
#### Nginx
* Install nginx, if not already done:
```shell
sudo apk add nginx
```
* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
```shell
sudo apk add certbot
```
and then set it up:
```shell
sudo mkdir -p /var/lib/letsencrypt/
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --standalone
```
If that doesn’t work, make sure, that nginx is not already running. If it still doesn’t work, try setting up nginx first (change ssl “on” to “off” and try again).
* Copy the example nginx configuration to the nginx folder
```shell
sudo cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/conf.d/pleroma.conf
```
* Before starting nginx edit the configuration and change it to your needs. You must change change `server_name` and the paths to the certificates. You can use `nano` (install with `apk add nano` if missing).
```
server {
server_name your.domain;
listen 80;
...
}
server {
server_name your.domain;
listen 443 ssl http2;
...
ssl_trusted_certificate /etc/letsencrypt/live/your.domain/chain.pem;
ssl_certificate /etc/letsencrypt/live/your.domain/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your.domain/privkey.pem;
...
}
```
* (Strongly recommended) serve media on another domain
Refer to the [Hardening your instance](../configuration/hardening.md) document on how to serve media on another domain. We STRONGLY RECOMMEND you to do this to minimize attack vectors.
* Enable and start nginx:
```shell
sudo rc-update add nginx
sudo service nginx start
```
If you need to renew the certificate in the future, uncomment the relevant location block in the nginx config and run:
```shell
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --webroot -w /var/lib/letsencrypt/
```
#### OpenRC service
* Copy example service file:
```shell
sudo cp /opt/pleroma/installation/init.d/pleroma /etc/init.d/pleroma
```
* Make sure to start it during the boot
```shell
sudo rc-update add pleroma
```
#### Create your first user
If your instance is up and running, you can create your first user with administrative rights with the following task:
```shell
sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress> --admin
```
#### Further reading
{! backend/installation/further_reading.include !}
## Questions
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
diff --git a/docs/installation/arch_linux_en.md b/docs/installation/arch_linux_en.md
index f7d722ef9..a9e6e8ca8 100644
--- a/docs/installation/arch_linux_en.md
+++ b/docs/installation/arch_linux_en.md
@@ -1,226 +1,225 @@
# Installing on Arch Linux
{! backend/installation/otp_vs_from_source_source.include !}
## Installation
This guide will assume that you have administrative rights, either as root or a user with [sudo permissions](https://wiki.archlinux.org/index.php/Sudo). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su <username> -s $SHELL -c 'command'` instead.
### Required packages
* `postgresql`
* `elixir`
* `git`
* `base-devel`
* `cmake`
* `file`
#### Optional packages used in this guide
* `nginx` (preferred, example configs for other reverse proxies can be found in the repo)
* `certbot` (or any other ACME client for Let’s Encrypt certificates)
* `ImageMagick`
* `ffmpeg`
-* `exiftool`
### Prepare the system
* First update the system, if not already done:
```shell
sudo pacman -Syu
```
* Install some of the above mentioned programs:
```shell
sudo pacman -S git base-devel elixir cmake file
```
### Install PostgreSQL
[Arch Wiki article](https://wiki.archlinux.org/index.php/PostgreSQL)
* Install the `postgresql` package:
```shell
sudo pacman -S postgresql
```
* Initialize the database cluster:
```shell
sudo -iu postgres initdb -D /var/lib/postgres/data
```
* Start and enable the `postgresql.service`
```shell
sudo systemctl enable --now postgresql.service
```
### Install media / graphics packages (optional, see [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md))
```shell
-sudo pacman -S ffmpeg imagemagick perl-image-exiftool
+sudo pacman -S ffmpeg imagemagick
```
### Install PleromaBE
* Add a new system user for the Pleroma service:
```shell
sudo useradd -r -s /bin/false -m -d /var/lib/pleroma -U pleroma
```
**Note**: To execute a single command as the Pleroma system user, use `sudo -Hu pleroma command`. You can also switch to a shell by using `sudo -Hu pleroma $SHELL`. If you don’t have and want `sudo` on your system, you can use `su` as root user (UID 0) for a single command by using `su -l pleroma -s $SHELL -c 'command'` and `su -l pleroma -s $SHELL` for starting a shell.
* Git clone the PleromaBE repository and make the Pleroma user the owner of the directory:
```shell
sudo mkdir -p /opt/pleroma
sudo chown -R pleroma:pleroma /opt/pleroma
sudo -Hu pleroma git clone -b stable https://git.pleroma.social/pleroma/pleroma /opt/pleroma
```
* Change to the new directory:
```shell
cd /opt/pleroma
```
* Install the dependencies for Pleroma and answer with `yes` if it asks you to install `Hex`:
```shell
sudo -Hu pleroma mix deps.get
```
* Generate the configuration: `sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen`
* Answer with `yes` if it asks you to install `rebar3`.
* This may take some time, because parts of pleroma get compiled first.
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
* Check the configuration and if all looks right, rename it, so Pleroma will load it (`prod.secret.exs` for productive instance, `dev.secret.exs` for development instances):
```shell
sudo -Hu pleroma mv config/{generated_config.exs,prod.secret.exs}
```
* The previous command creates also the file `config/setup_db.psql`, with which you can create the database:
```shell
sudo -Hu postgres psql -f config/setup_db.psql
```
* Now run the database migration:
```shell
sudo -Hu pleroma MIX_ENV=prod mix ecto.migrate
```
* Now you can start Pleroma already
```shell
sudo -Hu pleroma MIX_ENV=prod mix phx.server
```
### Finalize installation
If you want to open your newly installed instance to the world, you should run nginx or some other webserver/proxy in front of Pleroma and you should consider to create a systemd service file for Pleroma.
#### Nginx
* Install nginx, if not already done:
```shell
sudo pacman -S nginx
```
* Create directories for available and enabled sites:
```shell
sudo mkdir -p /etc/nginx/sites-{available,enabled}
```
* Append the following line at the end of the `http` block in `/etc/nginx/nginx.conf`:
```Nginx
include sites-enabled/*;
```
* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
```shell
sudo pacman -S certbot certbot-nginx
```
and then set it up:
```shell
sudo mkdir -p /var/lib/letsencrypt/
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --standalone
```
If that doesn’t work, make sure, that nginx is not already running. If it still doesn’t work, try setting up nginx first (change ssl “on” to “off” and try again).
---
* Copy the example nginx configuration and activate it:
```shell
sudo cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/sites-available/pleroma.nginx
sudo ln -s /etc/nginx/sites-available/pleroma.nginx /etc/nginx/sites-enabled/pleroma.nginx
```
* Before starting nginx edit the configuration and change it to your needs (e.g. change servername, change cert paths)
* (Strongly recommended) serve media on another domain
Refer to the [Hardening your instance](../configuration/hardening.md) document on how to serve media on another domain. We STRONGLY RECOMMEND you to do this to minimize attack vectors.
* Enable and start nginx:
```shell
sudo systemctl enable --now nginx.service
```
If you need to renew the certificate in the future, uncomment the relevant location block in the nginx config and run:
```shell
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --webroot -w /var/lib/letsencrypt/
```
#### Other webserver/proxies
You can find example configurations for them in `/opt/pleroma/installation/`.
#### Systemd service
* Copy example service file
```shell
sudo cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service
```
* Edit the service file and make sure that all paths fit your installation
* Enable and start `pleroma.service`:
```shell
sudo systemctl enable --now pleroma.service
```
#### Create your first user
If your instance is up and running, you can create your first user with administrative rights with the following task:
```shell
sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress> --admin
```
#### Further reading
{! backend/installation/further_reading.include !}
## Questions
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
diff --git a/docs/installation/debian_based_en.md b/docs/installation/debian_based_en.md
index 30f48792d..d4787f263 100644
--- a/docs/installation/debian_based_en.md
+++ b/docs/installation/debian_based_en.md
@@ -1,195 +1,195 @@
# Installing on Debian Based Distributions
{! backend/installation/otp_vs_from_source_source.include !}
## Installation
This guide will assume you are on Debian 12 (“bookworm”) or later. This guide should also work with Ubuntu 22.04 (“jammy”) and later. It also assumes that you have administrative rights, either as root or a user with [sudo permissions](https://www.digitalocean.com/community/tutorials/how-to-add-delete-and-grant-sudo-privileges-to-users-on-a-debian-vps). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su <username> -s $SHELL -c 'command'` instead.
{! backend/installation/generic_dependencies.include !}
### Prepare the system
* First update the system, if not already done:
```shell
sudo apt update
sudo apt full-upgrade
```
* Install some of the above mentioned programs:
```shell
sudo apt install git build-essential postgresql postgresql-contrib cmake libmagic-dev
```
### Install Elixir and Erlang
* Install Elixir and Erlang (you might need to use backports or [asdf](https://github.com/asdf-vm/asdf) on old systems):
```shell
sudo apt update
sudo apt install elixir erlang-dev erlang-nox
```
### Optional packages: [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md)
```shell
-sudo apt install imagemagick ffmpeg libimage-exiftool-perl
+sudo apt install imagemagick ffmpeg
```
### Install PleromaBE
* Add a new system user for the Pleroma service:
```shell
sudo useradd -r -s /bin/false -m -d /var/lib/pleroma -U pleroma
```
**Note**: To execute a single command as the Pleroma system user, use `sudo -Hu pleroma command`. You can also switch to a shell by using `sudo -Hu pleroma $SHELL`. If you don’t have and want `sudo` on your system, you can use `su` as root user (UID 0) for a single command by using `su -l pleroma -s $SHELL -c 'command'` and `su -l pleroma -s $SHELL` for starting a shell.
* Git clone the PleromaBE repository and make the Pleroma user the owner of the directory:
```shell
sudo mkdir -p /opt/pleroma
sudo chown -R pleroma:pleroma /opt/pleroma
sudo -Hu pleroma git clone -b stable https://git.pleroma.social/pleroma/pleroma /opt/pleroma
```
* Change to the new directory:
```shell
cd /opt/pleroma
```
* Install the dependencies for Pleroma and answer with `yes` if it asks you to install `Hex`:
```shell
sudo -Hu pleroma mix deps.get
```
* Generate the configuration:
```shell
sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen
```
* During this process:
* Answer with `yes` if it asks you to install `rebar3`.
* This may take some time, because parts of pleroma get compiled first.
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
* Check the configuration and if all looks right, rename it, so Pleroma will load it (`prod.secret.exs` for production instances, `dev.secret.exs` for development instances):
```shell
sudo -Hu pleroma mv config/{generated_config.exs,prod.secret.exs}
```
* The previous command creates also the file `config/setup_db.psql`, with which you can create the database:
```shell
sudo -Hu postgres psql -f config/setup_db.psql
```
* Now run the database migration:
```shell
sudo -Hu pleroma MIX_ENV=prod mix ecto.migrate
```
* Now you can start Pleroma already
```shell
sudo -Hu pleroma MIX_ENV=prod mix phx.server
```
### Finalize installation
If you want to open your newly installed instance to the world, you should run nginx or some other webserver/proxy in front of Pleroma and you should consider to create a systemd service file for Pleroma.
#### Nginx
* Install nginx, if not already done:
```shell
sudo apt install nginx
```
* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
```shell
sudo apt install certbot
```
and then set it up:
```shell
sudo mkdir -p /var/lib/letsencrypt/
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --standalone
```
If that doesn’t work, make sure, that nginx is not already running. If it still doesn’t work, try setting up nginx first (change ssl “on” to “off” and try again).
---
* Copy the example nginx configuration and activate it:
```shell
sudo cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/sites-available/pleroma.nginx
sudo ln -s /etc/nginx/sites-available/pleroma.nginx /etc/nginx/sites-enabled/pleroma.nginx
```
* Before starting nginx edit the configuration and change it to your needs (e.g. change servername, change cert paths)
* (Strongly recommended) serve media on another domain
Refer to the [Hardening your instance](../configuration/hardening.md) document on how to serve media on another domain. We STRONGLY RECOMMEND you to do this to minimize attack vectors.
* Enable and start nginx:
```shell
sudo systemctl enable --now nginx.service
```
If you need to renew the certificate in the future, uncomment the relevant location block in the nginx config and run:
```shell
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --webroot -w /var/lib/letsencrypt/
```
#### Other webserver/proxies
You can find example configurations for them in `/opt/pleroma/installation/`.
#### Systemd service
* Copy example service file
```shell
sudo cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service
```
* Edit the service file and make sure that all paths fit your installation
* Enable and start `pleroma.service`:
```shell
sudo systemctl enable --now pleroma.service
```
#### Create your first user
If your instance is up and running, you can create your first user with administrative rights with the following task:
```shell
sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress> --admin
```
#### Further reading
{! backend/installation/further_reading.include !}
## Questions
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
diff --git a/docs/installation/debian_based_jp.md b/docs/installation/debian_based_jp.md
index 5b261296d..6cb1340c6 100644
--- a/docs/installation/debian_based_jp.md
+++ b/docs/installation/debian_based_jp.md
@@ -1,198 +1,197 @@
# Pleromaの入れ方
Note: This article is potentially outdated because at this time we may not have people who can speak this language well enough to update it. To see the up-to-date version, which may have significant differences or important caveats of the installation process, look up the English version.
## 日本語訳について
この記事は [Installing on Debian based distributions](Installing on Debian based distributions) の日本語訳です。何かがおかしいと思ったら、原文を見てください。
## インストール
このガイドはDebian Bookwormを利用することを想定しています。Ubuntu 22.04でもおそらく動作します。また、ユーザはrootもしくはsudoにより管理者権限を持っていることを前提とします。もし、以下の操作をrootユーザで行う場合は、 `sudo` を無視してください。ただし、`sudo -Hu pleroma` のようにユーザを指定している場合には `su <username> -s $SHELL -c 'command'` を代わりに使ってください。
### 必要なソフトウェア
- PostgreSQL 11.0以上 (Ubuntu16.04では9.5しか提供されていないので,[](https://www.postgresql.org/download/linux/ubuntu/)こちらから新しいバージョンを入手してください)
- `postgresql-contrib` 11.0以上 (同上)
- Elixir 1.15 以上 ([Debianのリポジトリからインストールしないこと!!! ここからインストールすること!](https://elixir-lang.org/install.html#unix-and-unix-like)。または [asdf](https://github.com/asdf-vm/asdf) をpleromaユーザーでインストールしてください)
- `erlang-dev`
- `erlang-nox`
- `git`
- `build-essential`
- `cmake`
- `libmagic-dev`
#### このガイドで利用している追加パッケージ
- `nginx` (おすすめです。他のリバースプロキシを使う場合は、参考となる設定をこのリポジトリから探してください)
- `certbot` (または何らかのLet's Encrypt向けACMEクライアント)
- `ImageMagick`
- `ffmpeg`
-- `exiftool`
### システムを準備する
* まずシステムをアップデートしてください。
```
sudo apt update
sudo apt full-upgrade
```
* 上記に挙げたパッケージをインストールしておきます。
```
sudo apt install git build-essential postgresql postgresql-contrib cmake ffmpeg imagemagick libmagic-dev
```
### ElixirとErlangをインストールします
* Erlangのリポジトリをダウンロードおよびインストールします。
```
wget -P /tmp/ https://packages.erlang-solutions.com/erlang-solutions_2.0_all.deb
sudo dpkg -i /tmp/erlang-solutions_2.0_all.deb
```
* ElixirとErlangをインストールします、
```
sudo apt update
sudo apt install elixir erlang-dev erlang-nox
```
### オプションパッケージ: [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md)
```shell
-sudo apt install imagemagick ffmpeg libimage-exiftool-perl
+sudo apt install imagemagick ffmpeg
```
### Pleroma BE (バックエンド) をインストールします
* Pleroma用に新しいユーザーを作ります。
```
sudo useradd -r -s /bin/false -m -d /var/lib/pleroma -U pleroma
```
**注意**: Pleromaユーザとして単発のコマンドを実行したい場合はは、`sudo -Hu pleroma command` を使ってください。シェルを使いたい場合は `sudo -Hu pleroma $SHELL`です。もし `sudo` を使わない場合は、rootユーザで `su -l pleroma -s $SHELL -c 'command'` とすることでコマンドを、`su -l pleroma -s $SHELL` とすることでシェルを開始できます。
* Gitリポジトリをクローンします。
```
sudo mkdir -p /opt/pleroma
sudo chown -R pleroma:pleroma /opt/pleroma
sudo -Hu pleroma git clone -b stable https://git.pleroma.social/pleroma/pleroma /opt/pleroma
```
* 新しいディレクトリに移動します。
```
cd /opt/pleroma
```
* Pleromaが依存するパッケージをインストールします。Hexをインストールしてもよいか聞かれたら、yesを入力してください。
```
sudo -Hu pleroma mix deps.get
```
* コンフィギュレーションを生成します。
```
sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen
```
* rebar3をインストールしてもよいか聞かれたら、yesを入力してください。
* このときにpleromaの一部がコンパイルされるため、この処理には時間がかかります。
* あなたのインスタンスについて、いくつかの質問されます。この質問により `config/generated_config.exs` という設定ファイルが生成されます。
* コンフィギュレーションを確認して、もし問題なければ、ファイル名を変更してください。
```
sudo -Hu pleroma mv config/{generated_config.exs,prod.secret.exs}
```
* 先程のコマンドで、すでに `config/setup_db.psql` というファイルが作られています。このファイルをもとに、データベースを作成します。
```
sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen
```
* そして、データベースのマイグレーションを実行します。
```
sudo -Hu pleroma MIX_ENV=prod mix ecto.migrate
```
* これでPleromaを起動できるようになりました。
```
sudo -Hu pleroma MIX_ENV=prod mix phx.server
```
### インストールの最終段階
あなたの新しいインスタンスを世界に向けて公開するには、nginx等のWebサーバやプロキシサーバをPleromaの前段に使用する必要があります。また、Pleroma のためにシステムサービスファイルを作成する必要があります。
#### Nginx
* まだインストールしていないなら、nginxをインストールします。
```
sudo apt install nginx
```
* SSLをセットアップします。他の方法でもよいですが、ここではcertbotを説明します。
certbotを使うならば、まずそれをインストールします。
```
sudo apt install certbot
```
そしてセットアップします。
```
sudo mkdir -p /var/lib/letsencrypt/
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --standalone
```
もしうまくいかないときは、nginxが正しく動いていない可能性があります。先にnginxを設定してください。ssl "on" を "off" に変えてから再試行してください。
---
* nginxの設定ファイルサンプルをnginxフォルダーにコピーします。
```
sudo cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/sites-available/pleroma.nginx
sudo ln -s /etc/nginx/sites-available/pleroma.nginx /etc/nginx/sites-enabled/pleroma.nginx
```
* nginxを起動する前に、設定ファイルを編集してください。例えば、サーバー名、証明書のパスなどを変更する必要があります。
* nginxを再起動します。
```
sudo systemctl enable --now nginx.service
```
もし証明書を更新する必要が出てきた場合には、nginxの関連するlocationブロックのコメントアウトを外し、以下のコマンドを動かします。
```
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --webroot -w /var/lib/letsencrypt/
```
#### 他のWebサーバやプロキシ
これに関してはサンプルが `/opt/pleroma/installation/` にあるので、探してみてください。
#### Systemd サービス
* サービスファイルのサンプルをコピーします。
```
sudo cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service
```
* サービスファイルを変更します。すべてのパスが正しいことを確認してください
* サービスを有効化し `pleroma.service` を開始してください
```
sudo systemctl enable --now pleroma.service
```
#### 初期ユーザの作成
新たにインスタンスを作成したら、以下のコマンドにより管理者権限を持った初期ユーザを作成できます。
```
sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress> --admin
```
#### その他の設定とカスタマイズ
{! backend/installation/further_reading.include !}
## 質問ある?
インストールについて質問がある、もしくは、うまくいかないときは、以下のところで質問できます。
* [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat)
* **libera.chat** の **#pleroma** IRCチャンネル
diff --git a/docs/installation/freebsd_en.md b/docs/installation/freebsd_en.md
index 920bc7d35..2b857b9a9 100644
--- a/docs/installation/freebsd_en.md
+++ b/docs/installation/freebsd_en.md
@@ -1,223 +1,223 @@
# Installing on FreeBSD
This document was written for FreeBSD 12.1, but should be work on future releases.
{! backend/installation/generic_dependencies.include !}
## Installing software used in this guide
This assumes the target system has `pkg(8)`.
```
# pkg install elixir postgresql12-server postgresql12-client postgresql12-contrib git-lite sudo nginx gmake acme.sh cmake vips
```
Copy the rc.d scripts to the right directory:
Setup the required services to automatically start at boot, using `sysrc(8)`.
```
# sysrc nginx_enable=YES
# sysrc postgresql_enable=YES
```
## Initialize postgres
```
# service postgresql initdb
# service postgresql start
```
### Install media / graphics packages (optional, see [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md))
```shell
-# pkg install imagemagick ffmpeg p5-Image-ExifTool vips
+# pkg install imagemagick ffmpeg vips
```
## Configuring Pleroma
Create a user for Pleroma:
```
# pw add user pleroma -m
# echo 'export LC_ALL="en_US.UTF-8"' >> /home/pleroma/.profile
# echo 'export VIX_COMPILATION_MODE=PLATFORM_PROVIDED_LIBVIPS' >> /home/pleroma/.profile
# su -l pleroma
```
Clone the repository:
```
$ cd $HOME # Should be the same as /home/pleroma
$ git clone -b stable https://git.pleroma.social/pleroma/pleroma.git
```
Configure Pleroma. Note that you need a domain name at this point:
```
$ cd /home/pleroma/pleroma
$ mix deps.get # Enter "y" when asked to install Hex
$ MIX_ENV=prod mix pleroma.instance gen # You will be asked a few questions here.
$ cp config/generated_config.exs config/prod.secret.exs
```
Since Postgres is configured, we can now initialize the database. There should
now be a file in `config/setup_db.psql` that makes this easier. Edit it, and
*change the password* to a password of your choice. Make sure it is secure, since
it'll be protecting your database. As root, you can now initialize the database:
```
# cd /home/pleroma/pleroma
# sudo -Hu postgres -g postgres psql -f config/setup_db.psql
```
Postgres allows connections from all users without a password by default. To
fix this, edit `/var/db/postgres/data12/pg_hba.conf`. Change every `trust` to
`password`.
Once this is done, restart Postgres with:
```
# service postgresql restart
```
Run the database migrations.
Back as the pleroma user, run the following to implement any database migrations.
```
# su -l pleroma
$ cd /home/pleroma/pleroma
$ MIX_ENV=prod mix ecto.migrate
```
You will need to do this whenever you update with `git pull`:
## Configuring acme.sh
We'll be using acme.sh in Stateless Mode for TLS certificate renewal.
First, as root, allow the user `acme` to have access to the acme log file, as follows:
```
# touch /var/log/acme.sh.log
# chown acme:acme /var/log/acme.sh.log
# chmod 600 /var/log/acme.sh.log
```
Next, obtain your account fingerprint:
```
# sudo -Hu acme -g acme acme.sh --register-account
```
You need to add the following to your nginx configuration for the server
running on port 80:
```
location ~ ^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)$ {
default_type text/plain;
return 200 "$1.6fXAG9VyG0IahirPEU2ZerUtItW2DHzDzD9wZaEKpqd";
}
```
Replace the string after after `$1.` with your fingerprint.
Start nginx:
```
# service nginx start
```
It should now be possible to issue a cert (replace `example.com`
with your domain name):
```
# sudo -Hu acme -g acme acme.sh --issue -d example.com --stateless
```
Let's add auto-renewal to `/etc/crontab`
(replace `example.com` with your domain):
```
/usr/local/bin/sudo -Hu acme -g acme /usr/local/sbin/acme.sh -r -d example.com --stateless
```
### Configuring nginx
FreeBSD's default nginx configuration does not contain an include directive, which is
typically used for multiple sites. Therefore, you will need to first create the required
directory as follows:
```
# mkdir -p /usr/local/etc/nginx/sites-available
```
Next, add an `include` directive to `/usr/local/etc/nginx/nginx.conf`, within the `http {}`
block, as follows:
```
http {
...
include /usr/local/etc/nginx/sites-available/*;
}
```
As root, copy `/home/pleroma/pleroma/installation/pleroma.nginx` to
`/usr/local/etc/nginx/sites-available/pleroma.nginx`.
Edit the defaults of `/usr/local/etc/nginx/sites-available/pleroma.nginx`:
* Change `ssl_trusted_certificate` to `/var/db/acme/certs/example.tld/example.tld.cer`.
* Change `ssl_certificate` to `/var/db/acme/certs/example.tld/fullchain.cer`.
* Change `ssl_certificate_key` to `/var/db/acme/certs/example.tld/example.tld.key`.
* Change all references of `example.tld` to your instance's domain name.
#### (Strongly recommended) serve media on another domain
Refer to the [Hardening your instance](../configuration/hardening.md) document on how to serve media on another domain. We STRONGLY RECOMMEND you to do this to minimize attack vectors.
## Creating a startup script for Pleroma
Pleroma will need to compile when it initially starts, which typically takes a longer
period of time. Therefore, it is good practice to initially run pleroma from the
command-line before utilizing the rc.d script. That is done as follows:
```
# su -l pleroma
$ cd $HOME/pleroma
$ MIX_ENV=prod mix phx.server
```
Copy the startup script to the correct location and make sure it's executable:
```
# cp /home/pleroma/pleroma/installation/freebsd/rc.d/pleroma /usr/local/etc/rc.d/pleroma
# chmod +x /usr/local/etc/rc.d/pleroma
```
Update the `/etc/rc.conf` and start pleroma with the following commands:
```
# sysrc pleroma_enable=YES
# service pleroma start
```
#### Create your first user
If your instance is up and running, you can create your first user with administrative rights with the following task:
```shell
sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress> --admin
```
## Conclusion
Restart nginx with `# service nginx restart` and you should be up and running.
Make sure your time is in sync, or other instances will receive your posts with
incorrect timestamps. You should have ntpd running.
## Questions
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
diff --git a/docs/installation/generic_dependencies.include b/docs/installation/generic_dependencies.include
index 588a6c019..84e92ecaf 100644
--- a/docs/installation/generic_dependencies.include
+++ b/docs/installation/generic_dependencies.include
@@ -1,16 +1,15 @@
## Required dependencies
* PostgreSQL >=11.0
* Elixir >=1.15.0 <1.19
* Erlang OTP >=23.0.0 (supported: <28)
* git
* file / libmagic
* gcc or clang
* GNU make
* CMake
## Optional dependencies
* ImageMagick
* FFmpeg
-* exiftool
diff --git a/docs/installation/gentoo_en.md b/docs/installation/gentoo_en.md
index dc47d27f8..066497b85 100644
--- a/docs/installation/gentoo_en.md
+++ b/docs/installation/gentoo_en.md
@@ -1,308 +1,307 @@
# Manual install on Gentoo GNU/Linux
{! backend/installation/otp_vs_from_source.include !}
This guide covers a manual from-source installation. To use the gentoo package, please check the [packaged installation guide for gentoo](./gentoo_otp_en.md).
## Installation
This guide will assume that you have administrative rights, either as root or a user with [sudo permissions](https://wiki.gentoo.org/wiki/Sudo). Lines that begin with `#` indicate that they should be run as the superuser. Lines using `$` should be run as the indicated user, e.g. `pleroma$` should be run as the `pleroma` user.
{! backend/installation/generic_dependencies.include !}
### Your make.conf, package.use, and USE flags
The only specific USE flag you should need is the `uuid` flag for `dev-db/postgresql`. Add the following line to any new file in `/etc/portage/package.use`. If you would like a suggested name for the file, either `postgresql` or `pleroma` would do fine, depending on how you like to arrange your package.use flags.
```text
dev-db/postgresql uuid
```
You could opt to add `USE="uuid"` to `/etc/portage/make.conf` if you'd rather set this as a global USE flags, but this flags does unrelated things in other packages, so keep that in mind if you elect to do so.
Double check your compiler flags in `/etc/portage/make.conf`. If you require any special compilation flags or would like to set up remote builds, now is the time to do so. Be sure that your CFLAGS and MAKEOPTS make sense for the platform you are using. It is not recommended to use above `-O2` or risky optimization flags for a production server.
### Installing a cron daemon
Gentoo quite pointedly does not come with a cron daemon installed, and as such it is recommended you install one to automate certbot renewals and to allow other system administration tasks to be run automatically. Gentoo has [a whole wide world of cron options](https://wiki.gentoo.org/wiki/Cron) but if you just want A Cron That Works, `emerge --ask virtual/cron` will install the default cron implementation (probably cronie) which will work just fine. For the purpouses of this guide, we will be doing just that.
### Required ebuilds
* `dev-db/postgresql`
* `dev-lang/elixir`
* `dev-vcs/git`
* `dev-util/cmake`
* `sys-apps/file`
#### Optional ebuilds used in this guide
* `www-servers/nginx` (preferred, example configs for other reverse proxies can be found in the repo)
* `app-crypt/certbot` (or any other ACME client for Let’s Encrypt certificates)
* `app-crypt/certbot-nginx` (nginx certbot plugin that allows use of the all-powerful `--nginx` flag on certbot)
* `media-gfx/imagemagick`
* `media-video/ffmpeg`
-* `media-libs/exiftool`
### Prepare the system
* First ensure that you have the latest copy of the portage ebuilds if you have not synced them yet:
```shell
# emaint sync -a
```
* Emerge all required the required and suggested software in one go:
```shell
# emerge --ask dev-db/postgresql dev-lang/elixir dev-vcs/git www-servers/nginx app-crypt/certbot app-crypt/certbot-nginx dev-util/cmake sys-apps/file
```
If you would not like to install the optional packages, remove them from this line.
If you're running this from a low-powered virtual machine, it should work though it will take some time. There were no issues on a VPS with a single core and 1GB of RAM; if you are using an even more limited device and run into issues, you can try creating a swapfile or use a more powerful machine running Gentoo to [cross build](https://wiki.gentoo.org/wiki/Cross_build_environment). If you have a wait ahead of you, now would be a good time to take a break, stretch a bit, refresh your beverage of choice and/or get a snack, and reply to Arch users' posts with "I use Gentoo btw" as we do.
### Install PostgreSQL
[Gentoo Wiki article](https://wiki.gentoo.org/wiki/PostgreSQL) as well as [PostgreSQL QuickStart](https://wiki.gentoo.org/wiki/PostgreSQL/QuickStart) might be worth a quick glance, as the way Gentoo handles postgres is slightly unusual, with built in capability to have two different databases running for testing and live or whatever other purpouse. While it is still straightforward to install, it does mean that the version numbers used in this guide might change for future updates, so keep an eye out for the output you get from `emerge` to ensure you are using the correct ones.
* Install postgresql if you have not done so already:
```shell
# emerge --ask dev-db/postgresql
```
Ensure that `/etc/conf.d/postgresql-11` has the encoding you want (it defaults to UTF8 which is probably what you want) and make any adjustments to the data directory if you find it necessary. Be sure to adjust the number at the end depending on what version of postgres you actually installed.
* Initialize the database cluster
The output from emerging postgresql should give you a command for initializing the postgres database. The default slot should be indicated in this command, ensure that it matches the command below.
```shell
# emerge --config dev-db/postgresql:11
```
* Start postgres and enable the system service
```shell
# /etc/init.d/postgresql-11 start
# rc-update add postgresql-11 default
```
### A note on licenses, the AGPL, and deployment procedures
If you do not plan to make any modifications to your Pleroma instance, cloning directly from the main repo will get you what you need. However, if you plan on doing any contributions to upstream development, making changes or modifications to your instance, making custom themes, or want to play around--and let's be honest here, if you're using Gentoo that is most likely you--you will save yourself a lot of headache later if you take the time right now to fork the Pleroma repo and use that in the following section.
Not only does this make it much easier to deploy changes you make, as you can commit and pull from upstream and all that good stuff from the comfort of your local machine then simply `git pull` on your instance server when you're ready to deploy, it also ensures you are compliant with the Affero General Public Licence that Pleroma is licenced under, which stipulates that all network services provided with modified AGPL code must publish their changes on a publicly available internet service and for free. It also makes it much easier to ask for help from and provide help to your fellow Pleroma admins if your public repo always reflects what you are running because it is part of your deployment procedure.
### Install media / graphics packages (optional, see [`docs/installation/optional/media_graphics_packages.md`](docs/installation/optional/media_graphics_packages.md))
```shell
-# emerge --ask media-video/ffmpeg media-gfx/imagemagick media-libs/exiftool
+# emerge --ask media-video/ffmpeg media-gfx/imagemagick
```
### Install PleromaBE
* Add a new system user for the Pleroma service and set up default directories:
Remove `,wheel` if you do not want this user to be able to use `sudo`, however note that being able to `sudo` as the `pleroma` user will make finishing the installation and common maintenance tasks somewhat easier:
```shell
# useradd -m -G users,wheel -s /bin/bash pleroma
```
Optional: If you are using sudo, review your sudo setup to ensure it works for you. The `/etc/sudoers` file has a lot of options and examples to help you, and [the Gentoo sudo guide](https://wiki.gentoo.org/wiki/Sudo) has more information. Finishing this installation will be somewhat easier if you have a way to sudo from the `pleroma` user, but it might be best to not allow that user to sudo during normal operation, and as such there will be a reminder at the end of this guide to double check if you would like to lock down the `pleroma` user after initial setup.
**Note**: To execute a single command as the Pleroma system user, use `sudo -Hu pleroma command`. You can also switch to a shell by using `sudo -Hu pleroma $SHELL`. If you don't have or want `sudo` or would like to use the system as the `pleroma` user for instance maintenance tasks, you can simply use `su - pleroma` to switch to the `pleroma` user.
* Git clone the PleromaBE repository and make the Pleroma user the owner of the directory:
It is highly recommended you use your own fork for the `https://path/to/repo` part below, however if you foolishly decide to forego using your own fork, the primary repo `https://git.pleroma.social/pleroma/pleroma` will work here.
```shell
pleroma$ cd ~
pleroma$ git clone -b stable https://path/to/repo
```
* Change to the new directory:
```shell
pleroma$ cd ~/pleroma
```
* Install the dependencies for Pleroma and answer with `yes` if it asks you to install `Hex`:
```shell
pleroma$ mix deps.get
```
* Generate the configuration:
```shell
pleroma$ MIX_ENV=prod mix pleroma.instance gen
```
* Answer with `yes` if it asks you to install `rebar3`.
* This part precompiles some parts of Pleroma, so it might take a few moments
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
* Spend some time with `generated_config.exs` to ensure that everything is in order. If you plan on using an S3-compatible service to store your local media, that can be done here. You will likely mostly be using `prod.secret.exs` for a production instance, however if you would like to set up a development environment, make a copy to `dev.secret.exs` and adjust settings as needed as well.
```shell
pleroma$ mv config/generated_config.exs config/prod.secret.exs
```
* The previous command creates also the file `config/setup_db.psql`, with which you can create the database. Ensure that it is using the correct database name on the `CREATE DATABASE` and the `\c` lines, then run the postgres script:
```shell
pleroma$ sudo -Hu postgres psql -f config/setup_db.psql
```
* Now run the database migration:
```shell
pleroma$ MIX_ENV=prod mix ecto.migrate
```
* Now you can start Pleroma already
```shell
pleroma$ MIX_ENV=prod mix phx.server
```
It probably won't work over the public internet quite yet, however, as we still need to set up a web servere to proxy to the pleroma application, as well as configure SSL.
### Finalize installation
Assuming you want to open your newly installed federated social network to, well, the federation, you should run nginx or some other webserver/proxy in front of Pleroma. It is also a good idea to set up Pleroma to run as a system service.
#### Nginx
* Install nginx, if not already done:
```shell
# emerge --ask www-servers/nginx
```
* Create directories for available and enabled sites:
```shell
# mkdir -p /etc/nginx/sites-{available,enabled}
```
* Append the following line at the end of the `http` block in `/etc/nginx/nginx.conf`:
```Nginx
include sites-enabled/*;
```
* Setup your SSL cert, using your method of choice or certbot. If using certbot, install it if you haven't already:
```shell
# emerge --ask app-crypt/certbot app-crypt/certbot-nginx
```
and then set it up:
```shell
# mkdir -p /var/lib/letsencrypt/
# certbot certonly --email <your@emailaddress> -d <yourdomain> --standalone
```
If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. If that doesn’t work, make sure, that nginx is not already running. If it still doesn’t work, try setting up nginx first (change ssl “on” to “off” and try again). Often the answer to issues with certbot is to use the `--nginx` flag once you have nginx up and running.
If you are using any additional subdomains, such as for a media proxy, you can re-run the same command with the subdomain in question. When it comes time to renew later, you will not need to run multiple times for each domain, one renew will handle it.
---
* Copy the example nginx configuration and activate it:
```shell
# cp /home/pleroma/pleroma/installation/pleroma.nginx /etc/nginx/sites-available/
# ln -s /etc/nginx/sites-available/pleroma.nginx /etc/nginx/sites-enabled/pleroma.nginx
```
* Take some time to ensure that your nginx config is correct
Replace all instances of `example.tld` with your instance's public URL. If for whatever reason you made changes to the port that your pleroma app runs on, be sure that is reflected in your configuration.
Pay special attention to the line that begins with `ssl_ecdh_curve`. It is stongly advised to comment that line out so that OpenSSL will use its full capabilities, and it is also possible you are running OpenSSL 1.0.2 necessitating that you do this.
* (Strongly recommended) serve media on another domain
Refer to the [Hardening your instance](../configuration/hardening.md) document on how to serve media on another domain. We STRONGLY RECOMMEND you to do this to minimize attack vectors.
* Enable and start nginx:
```shell
# rc-update add nginx default
# /etc/init.d/nginx start
```
If you are using certbot, it is HIGHLY recommend you set up a cron job that renews your certificate, and that you install the suggested `certbot-nginx` plugin. If you don't do these things, you only have yourself to blame when your instance breaks suddenly because you forgot about it.
First, ensure that the command you will be installing into your crontab works.
```shell
# /usr/bin/certbot renew --nginx
```
Assuming not much time has passed since you got certbot working a few steps ago, you should get a message for all domains you installed certificates for saying `Cert not yet due for renewal`.
Now, run crontab as a superuser with `crontab -e` or `sudo crontab -e` as appropriate, and add the following line to your cron:
```cron
0 0 1 * * /usr/bin/certbot renew --nginx
```
This will run certbot on the first of the month at midnight. If you'd rather run more frequently, it's not a bad idea, feel free to go for it.
#### Other webserver/proxies
If you would like to use other webservers or proxies, there are example configurations for some popular alternatives in `/home/pleroma/pleroma/installation/`. You can, of course, check out [the Gentoo wiki](https://wiki.gentoo.org) for more information on installing and configuring said alternatives.
#### Create the uploads folder
Even if you are using S3, Pleroma needs someplace to store media posted on your instance. If you are using the `/home/pleroma/pleroma` root folder suggested by this guide, simply:
```shell
pleroma$ mkdir -p ~/pleroma/uploads
```
#### init.d service
* Copy example service file
```shell
# cp /home/pleroma/pleroma/installation/init.d/pleroma /etc/init.d/
```
* Be sure to take a look at this service file and make sure that all paths fit your installation
* Enable and start `pleroma`:
```shell
# rc-update add pleroma default
# /etc/init.d/pleroma start
```
#### Create your first user
If your instance is up and running, you can create your first user with administrative rights with the following task:
```shell
pleroma$ MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress> --admin
```
#### Privilege cleanup
If you opted to allow sudo for the `pleroma` user but would like to remove the ability for greater security, now might be a good time to edit `/etc/sudoers` and/or change the groups the `pleroma` user belongs to. Be sure to restart the pleroma service afterwards to ensure it picks up on the changes.
#### Further reading
{! backend/installation/further_reading.include !}
## Questions
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
diff --git a/docs/installation/gentoo_otp_en.md b/docs/installation/gentoo_otp_en.md
index 20d8835da..352d9b807 100644
--- a/docs/installation/gentoo_otp_en.md
+++ b/docs/installation/gentoo_otp_en.md
@@ -1,207 +1,206 @@
# Packaged install on Gentoo Linux
{! backend/installation/otp_vs_from_source.include !}
This guide covers installation via Gentoo provided packaging. A [manual installation guide for gentoo](./gentoo_en.md) is also available.
## Installation
This guide will assume that you have administrative rights, either as root or a user with [sudo permissions](https://wiki.gentoo.org/wiki/Sudo). Lines that begin with `#` indicate that they should be run as the superuser. Lines using `$` should be run as the indicated user, e.g. `pleroma$` should be run as the `pleroma` user.
{! backend/installation/generic_dependencies.include !}
### Installing a cron daemon
Gentoo quite pointedly does not come with a cron daemon installed, and as such it is recommended you install one to automate certbot renewals and to allow other system administration tasks to be run automatically. Gentoo has [a whole wide world of cron options](https://wiki.gentoo.org/wiki/Cron) but if you just want A Cron That Works, `emerge --ask virtual/cron` will install the default cron implementation (probably cronie) which will work just fine. For the purpouses of this guide, we will be doing just that.
### Required ebuilds
* `www-apps/pleroma`
#### Optional ebuilds used in this guide
* `www-servers/nginx` (preferred, example configs for other reverse proxies can be found in the repo)
* `app-crypt/certbot` (or any other ACME client for Let’s Encrypt certificates)
* `app-crypt/certbot-nginx` (nginx certbot plugin that allows use of the all-powerful `--nginx` flag on certbot)
* `media-gfx/imagemagick`
* `media-video/ffmpeg`
-* `media-libs/exiftool`
### Prepare the system
* If you haven't yet done so, add the [Gentoo User Repository (GURU)](https://wiki.gentoo.org/wiki/Project:GURU), where the `www-apps/pleroma` ebuild currently lives at:
```shell
# eselect repository enable guru
```
* Ensure that you have the latest copy of the Gentoo and GURU ebuilds if you have not synced them yet:
```shell
# emaint sync -a
```
* Emerge all required the required and suggested software in one go:
```shell
# emerge --ask www-apps/pleroma www-servers/nginx app-crypt/certbot app-crypt/certbot-nginx
```
If you would not like to install the optional packages, remove them from this line.
If you're running this from a low-powered virtual machine, it should work though it will take some time. There were no issues on a VPS with a single core and 1GB of RAM; if you are using an even more limited device and run into issues, you can try creating a swapfile or use a more powerful machine running Gentoo to [cross build](https://wiki.gentoo.org/wiki/Cross_build_environment). If you have a wait ahead of you, now would be a good time to take a break, stretch a bit, refresh your beverage of choice and/or get a snack, and reply to Arch users' posts with "I use Gentoo btw" as we do.
### Setup PostgreSQL
[Gentoo Wiki article](https://wiki.gentoo.org/wiki/PostgreSQL) as well as [PostgreSQL QuickStart](https://wiki.gentoo.org/wiki/PostgreSQL/QuickStart) might be worth a quick glance, as the way Gentoo handles postgres is slightly unusual, with built in capability to have two different databases running for testing and live or whatever other purpouse. While it is still straightforward to install, it does mean that the version numbers used in this guide might change for future updates, so keep an eye out for the output you get from `emerge` to ensure you are using the correct ones.
* Initialize the database cluster
The output from emerging postgresql should give you a command for initializing the postgres database. The default slot should be indicated in this command, ensure that it matches the command below.
```shell
# emerge --config dev-db/postgresql:11
```
### Install media / graphics packages (optional)
See [Optional software packages needed for specific functionality](optional/media_graphics_packages.md) for details.
```shell
-# emerge --ask media-video/ffmpeg media-gfx/imagemagick media-libs/exiftool
+# emerge --ask media-video/ffmpeg media-gfx/imagemagick
```
### Setup PleromaBE
* Generate the configuration:
```shell
# pleroma_ctl instance gen --output /etc/pleroma/config.exs --output-psql /tmp/setup_db.psql"
```
* Create the PostgreSQL database
```shell
# sudo -u postgres -s $SHELL -lc "psql -f /tmp/setup_db.psql"
```
* Now run the database migration:
```shell
# pleroma_ctl migrate
```
* Optional: If you have installed RUM indexes (`dev-db/rum`) you also need to run:
```
# sudo -Hu pleroma "pleroma_ctl migrate --migrations-path priv/repo/optional_migrations/rum_indexing/"
```
* Now you can start Pleroma already and add it in the default runlevel
```shell
# rc-service pleroma start
# rc-update add pleroma default
```
It probably won't work over the public internet quite yet, however, as we still need to set up a web server to proxy to the pleroma application, as well as configure SSL.
### Finalize installation
Assuming you want to open your newly installed federated social network to, well, the federation, you should run nginx or some other webserver/proxy in front of Pleroma. It is also a good idea to set up Pleroma to run as a system service.
#### Nginx
* Install nginx, if not already done:
```shell
# emerge --ask www-servers/nginx
```
* Create directories for available and enabled sites:
```shell
# mkdir -p /etc/nginx/sites-{available,enabled}
```
* Append the following line at the end of the `http` block in `/etc/nginx/nginx.conf`:
```Nginx
include sites-enabled/*;
```
* Setup your SSL cert, using your method of choice or certbot. If using certbot, install it if you haven't already:
```shell
# emerge --ask app-crypt/certbot app-crypt/certbot-nginx
```
and then set it up:
```shell
# mkdir -p /var/lib/letsencrypt/
# certbot certonly --email <your@emailaddress> -d <yourdomain> --standalone
```
If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. If that doesn’t work, make sure, that nginx is not already running. If it still doesn’t work, try setting up nginx first (change ssl “on” to “off” and try again). Often the answer to issues with certbot is to use the `--nginx` flag once you have nginx up and running.
If you are using any additional subdomains, such as for a media proxy, you can re-run the same command with the subdomain in question. When it comes time to renew later, you will not need to run multiple times for each domain, one renew will handle it.
---
* Copy the example nginx configuration and activate it:
```shell
# cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/sites-available/
# ln -s /etc/nginx/sites-available/pleroma.nginx /etc/nginx/sites-enabled/pleroma.nginx
```
* Take some time to ensure that your nginx config is correct
Replace all instances of `example.tld` with your instance's public URL. If for whatever reason you made changes to the port that your pleroma app runs on, be sure that is reflected in your configuration.
Pay special attention to the line that begins with `ssl_ecdh_curve`. It is stongly advised to comment that line out so that OpenSSL will use its full capabilities, and it is also possible you are running OpenSSL 1.0.2 necessitating that you do this.
* Enable and start nginx:
```shell
# rc-update add nginx default
# /etc/init.d/nginx start
```
If you are using certbot, it is HIGHLY recommend you set up a cron job that renews your certificate, and that you install the suggested `certbot-nginx` plugin. If you don't do these things, you only have yourself to blame when your instance breaks suddenly because you forgot about it.
First, ensure that the command you will be installing into your crontab works.
```shell
# /usr/bin/certbot renew --nginx
```
Assuming not much time has passed since you got certbot working a few steps ago, you should get a message for all domains you installed certificates for saying `Cert not yet due for renewal`.
Now, run crontab as a superuser with `crontab -e` or `sudo crontab -e` as appropriate, and add the following line to your cron:
```cron
0 0 1 * * /usr/bin/certbot renew --nginx
```
This will run certbot on the first of the month at midnight. If you'd rather run more frequently, it's not a bad idea, feel free to go for it.
#### Other webserver/proxies
If you would like to use other webservers or proxies, there are example configurations for some popular alternatives in `/opt/pleroma/installation/`. You can, of course, check out [the Gentoo wiki](https://wiki.gentoo.org) for more information on installing and configuring said alternatives.
#### Create your first user
If your instance is up and running, you can create your first user with administrative rights with the following task:
```shell
pleroma$ pleroma_ctl user new <username> <your@emailaddress> --admin
```
#### Further reading
{! backend/installation/further_reading.include !}
## Questions
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
diff --git a/docs/installation/netbsd_en.md b/docs/installation/netbsd_en.md
index 7c003700c..85b098da0 100644
--- a/docs/installation/netbsd_en.md
+++ b/docs/installation/netbsd_en.md
@@ -1,294 +1,293 @@
# Installing on NetBSD
{! backend/installation/generic_dependencies.include !}
# Installation options
Currently there are two options available for NetBSD: manual installation (from source) or using experimental package from [pkgsrc-wip](https://github.com/NetBSD/pkgsrc-wip/tree/master/pleroma).
WIP package can be installed via pkgsrc and can be crosscompiled for easier binary distribution. Source installation most probably will be restricted to a single machine.
## pkgsrc installation
WIP package creates Mix.Release (similar to how Docker images are built) but doesn't bundle Erlang runtime, listing it as a dependency instead. This allows for easier and more modular installations, especially on weaker machines. Currently this method also does not support all features of `pleroma_ctl` command (like changing installation type or managing frontends) as NetBSD is not yet a supported binary flavour of Pleroma's CI.
In any case, you can install it the same way as any other `pkgsrc-wip` package:
```
cd /usr/pkgsrc
git clone --depth 1 git://wip.pkgsrc.org/pkgsrc-wip.git wip
cp -rf wip/pleroma www
cp -rf wip/libvips graphics
cd /usr/pkgsrc/www/pleroma
bmake && bmake install
```
Use `bmake package` to create a binary package. This can come especially handy if you're targeting embedded or low-power systems and are crosscompiling on a more powerful machine.
> Note: Elixir has [endianness bug](https://github.com/elixir-lang/elixir/issues/2785) which requires it to be compiled on a machine with the same endianness. In other words, package crosscompiled on amd64 (little endian) won't work on powerpc or sparc machines (big endian). While _in theory™_ nothing catastrophic should happen, one can see that for example regexes won't work properly. Some distributions just strip this warning away, so it doesn't bother the users... anyway, you've been warned.
## Source installation
pkgin should have been installed by the NetBSD installer if you selected
the right options. If it isn't installed, install it using `pkg_add`.
Note that `postgresql11-contrib` is needed for the Postgres extensions
Pleroma uses.
> Note: you can use modern versions of PostgreSQL. In this case, just use `postgresql16-contrib` and so on.
The `mksh` shell is needed to run the Elixir `mix` script.
`# pkgin install acmesh elixir git-base git-docs mksh nginx postgresql11-server postgresql11-client postgresql11-contrib sudo ffmpeg4 ImageMagick`
You can also build these packages using pkgsrc:
```
databases/postgresql11-contrib
databases/postgresql11-client
databases/postgresql11-server
devel/git-base
devel/git-docs
devel/cmake
lang/elixir
security/acmesh
security/sudo
shells/mksh
www/nginx
```
Create a user for Pleroma:
```
# groupadd pleroma
# useradd -d /home/pleroma -m -g pleroma -s /usr/pkg/bin/mksh pleroma
# echo 'export LC_ALL="en_GB.UTF-8"' >> /home/pleroma/.profile
# su -l pleroma -c $SHELL
```
Clone the repository:
```
$ cd /home/pleroma
$ git clone -b stable https://git.pleroma.social/pleroma/pleroma.git
```
Get deps and compile:
```
$ cd /home/pleroma/pleroma
$ export MIX_ENV=prod
$ mix deps.get
$ mix compile
```
## Install media / graphics packages (optional, see [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md))
-`# pkgin install ImageMagick ffmpeg4 p5-Image-ExifTool`
+`# pkgin install ImageMagick ffmpeg4`
or via pkgsrc:
```
-graphics/p5-Image-ExifTool
graphics/ImageMagick
multimedia/ffmpeg4
```
# Configuration
## Understanding $PREFIX
From now on, you may encounter `$PREFIX` variable in the paths. This variable indicates your current local pkgsrc prefix. Usually it's `/usr/pkg` unless you configured it otherwise. Translating to pkgsrc's lingo, it's called `LOCALBASE`, which essentially means the same this. You may want to set it up for your local shell session (this uses `mksh` which should already be installed as one of the required dependencies):
```
$ export PREFIX=$(pkg_info -Q LOCALBASE mksh)
$ echo $PREFIX
/usr/pkg
```
## Setting up your instance
Now, you need to configure your instance. During this initial configuration, you will be asked some questions about your server. You will need a domain name at this point; it doesn't have to be deployed, but changing it later will be very cumbersome.
If you've installed via pkgsrc, `pleroma_ctl` should already be in your `PATH`; if you've installed from source, it's located at `/home/pleroma/pleroma/release/bin/pleroma_ctl`.
```
$ su -l pleroma
$ pleroma_ctl instance gen --output $PREFIX/etc/pleroma/config.exs --output-psql /tmp/setup_db.psql
```
During installation, you will be asked about static and upload directories. Don't forget to create them and update permissions:
```
mkdir -p /var/lib/pleroma/uploads
chown -R pleroma:pleroma /var/lib/pleroma
```
## Setting up the database
First, run `# /etc/rc.d/pgsql start`. Then, `$ sudo -Hu pgsql -g pgsql createdb`.
We can now initialize the database. You'll need to edit generated SQL file from the previous step. It's located at `/tmp/setup_db.psql`.
Edit this file, and *change the password* to a password of your choice. Make sure it is secure, since
it'll be protecting your database. Now initialize the database:
```
$ sudo -Hu pgsql -g pgsql psql -f /tmp/setup_db.psql
```
Postgres allows connections from all users without a password by default. To
fix this, edit `$PREFIX/pgsql/data/pg_hba.conf`. Change every `trust` to
`password`.
Once this is done, restart Postgres with `# /etc/rc.d/pgsql restart`.
Run the database migrations.
### pkgsrc installation
```
pleroma_ctl migrate
```
### Source installation
You will need to do this whenever you update with `git pull`:
```
$ cd /home/pleroma/pleroma
$ MIX_ENV=prod mix ecto.migrate
```
## Configuring nginx
Install the example configuration file
(`$PREFIX/share/examples/pleroma/pleroma.nginx` or `/home/pleroma/pleroma/installation/pleroma.nginx`) to
`$PREFIX/etc/nginx.conf`.
Note that it will need to be wrapped in a `http {}` block. You should add
settings for the nginx daemon outside of the http block, for example:
```
user nginx nginx;
error_log /var/log/nginx/error.log;
worker_processes 4;
events {
}
```
Edit the defaults:
* Change `ssl_certificate` and `ssl_trusted_certificate` to
`/etc/nginx/tls/fullchain`.
* Change `ssl_certificate_key` to `/etc/nginx/tls/key`.
* Change `example.tld` to your instance's domain name.
### (Strongly recommended) serve media on another domain
Refer to the [Hardening your instance](../configuration/hardening.md) document on how to serve media on another domain. We STRONGLY RECOMMEND you to do this to minimize attack vectors.
## Configuring acme.sh
We'll be using acme.sh in Stateless Mode for TLS certificate renewal.
First, get your account fingerprint:
```
$ sudo -Hu nginx -g nginx acme.sh --register-account
```
You need to add the following to your nginx configuration for the server
running on port 80:
```
location ~ ^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)$ {
default_type text/plain;
return 200 "$1.6fXAG9VyG0IahirPEU2ZerUtItW2DHzDzD9wZaEKpqd";
}
```
Replace the string after after `$1.` with your fingerprint.
Start nginx:
```
# /etc/rc.d/nginx start
```
It should now be possible to issue a cert (replace `example.com`
with your domain name):
```
$ sudo -Hu nginx -g nginx acme.sh --issue -d example.com --stateless
```
Let's add auto-renewal to `/etc/daily.local`
(replace `example.com` with your domain):
```
/usr/pkg/bin/sudo -Hu nginx -g nginx \
/usr/pkg/sbin/acme.sh -r \
-d example.com \
--cert-file /etc/nginx/tls/cert \
--key-file /etc/nginx/tls/key \
--ca-file /etc/nginx/tls/ca \
--fullchain-file /etc/nginx/tls/fullchain \
--stateless
```
## Autostart
For properly functioning instance, you will need pleroma (backend service), nginx (reverse proxy) and postgresql (database) services running. There's no requirement for them to reside on the same machine, but you have to provide autostart for each of them.
### nginx
```
# cp $PREFIX/share/examples/rc.d/nginx /etc/rc.d
# echo "nginx=YES" >> /etc/rc.conf
```
### postgresql
```
# cp $PREFIX/share/examples/rc.d/pgsql /etc/rc.d
# echo "pgsql=YES" >> /etc/rc.conf
```
### pleroma
First, copy the script (pkgsrc variant)
```
# cp $PREFIX/share/examples/pleroma/pleroma.rc /etc/rc.d/pleroma
```
or source variant
```
# cp /home/pleroma/pleroma/installation/netbsd/rc.d/pleroma /etc/rc.d/pleroma
# chmod +x /etc/rc.d/pleroma
```
Then, add the following to `/etc/rc.conf`:
```
pleroma=YES
```
## Conclusion
Run `# /etc/rc.d/pleroma start` to start Pleroma.
Restart nginx with `# /etc/rc.d/nginx restart` and you should be up and running.
Make sure your time is in sync, or other instances will receive your posts with
incorrect timestamps. You should have ntpd running.
## Instances running NetBSD
* <https://catgirl.science>
#### Further reading
{! backend/installation/further_reading.include !}
## Questions
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
diff --git a/docs/installation/openbsd_en.md b/docs/installation/openbsd_en.md
index 1de016cdd..b3bd31d33 100644
--- a/docs/installation/openbsd_en.md
+++ b/docs/installation/openbsd_en.md
@@ -1,384 +1,383 @@
# Installing on OpenBSD
{! backend/installation/otp_vs_from_source_source.include !}
This guide describes the installation and configuration of Pleroma (and the required software to run it) on a single OpenBSD 7.7 server.
For any additional information regarding commands and configuration files mentioned here, check the man pages [online](https://man.openbsd.org/) or directly on your server with the man command.
{! backend/installation/generic_dependencies.include !}
## Installation
### Preparing the system
#### Required software
To install required packages, run the following command:
```
# pkg_add elixir gmake git postgresql-server postgresql-contrib cmake libmagic libvips
```
Pleroma requires a reverse proxy, OpenBSD has relayd in base (and is used in this guide) and packages/ports are available for nginx (www/nginx) and apache (www/apache-httpd).
Independently of the reverse proxy, [acme-client(1)](https://man.openbsd.org/acme-client) can be used to get a certificate from Let's Encrypt.
#### Optional software
* ImageMagick
* ffmpeg
- * exiftool
To install the above:
```
-# pkg_add ImageMagick ffmpeg p5-Image-ExifTool
+# pkg_add ImageMagick ffmpeg
```
For more information read [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md):
### PostgreSQL
Switch to the \_postgresql user and initialize PostgreSQL:
```
# su _postgresql
$ initdb -D /var/postgresql/data -U postgres --encoding=utf-8 --lc-collate=C
```
Running PostgreSQL in a different directory than `/var/postgresql/data` requires changing the `daemon_flags` variable in the `/etc/rc.d/postgresql` script.
For security reasons it is recommended to change the authentication method for `local` and `host` connections with the localhost address to `scram-sha-256`.<br>
Do not forget to set a password for the `postgres` user before doing so, otherwise you won't be able to log back in unless you change the authentication method back to `trust`.<br>
Changing the password hashing algorithm is not needed.<br>
For more information [read](https://www.postgresql.org/docs/16/auth-pg-hba-conf.html) the PostgreSQL documentation.
Enable and start the postgresql service:
```
# rcctl enable postgresql
# rcctl start postgresql
```
To check that PostgreSQL started properly and didn't fail right after starting, run `# rcctl check postgresql` which should return `postgresql(ok)`.
### Configuring Pleroma
Pleroma will be run by a dedicated \_pleroma user. Before creating it, insert the following lines in `/etc/login.conf`:
```
pleroma:\
:datasize=1536M:\
:openfiles-max=4096:\
:openfiles-cur=1024:\
:setenv=LC_ALL=en_US.UTF-8,VIX_COMPILATION_MODE=PLATFORM_PROVIDED_LIBVIPS,MIX_ENV=prod:\
:tc=daemon:
```
This creates a "pleroma" login class and sets higher values than default for datasize and openfiles (see [login.conf(5)](https://man.openbsd.org/login.conf)), this is required to avoid having Pleroma crash some time after starting.
Create the \_pleroma user, assign it the pleroma login class and create its home directory (/home/\_pleroma/):
```
# useradd -m -L pleroma _pleroma
```
Switch to the _pleroma user:
```
# su -l _pleroma
```
Clone the Pleroma repository:
```
$ git clone -b stable https://git.pleroma.social/pleroma/pleroma.git
$ cd pleroma
```
Pleroma is now installed in /home/\_pleroma/pleroma/. To configure it run:
```
$ mix deps.get
$ MIX_ENV=prod mix pleroma.instance gen # You will be asked a few questions here.
$ cp config/generated_config.exs config/prod.secret.exs
```
Note: Answer yes when asked to install Hex and rebar3. This step might take some time as Pleroma gets compiled first.
Create the Pleroma database:
```
$ psql -U postgres -f config/setup_db.psql
```
Apply database migrations:
```
$ MIX_ENV=prod mix ecto.migrate
```
Note: You will need to run this step again when updating your instance to a newer version with `git pull` or `git checkout tags/NEW_VERSION`.
As \_pleroma in /home/\_pleroma/pleroma, you can now run `MIX_ENV=prod mix phx.server` to start your instance.
In another SSH session or a tmux window, check that it is working properly by running `ftp -MVo - http://127.0.0.1:4000/api/v1/instance`, you should get json output.
Double-check that the *uri* value near the bottom is your instance's domain name and the instance *title* are correct.
### Configuring acme-client
acme-client is used to get SSL/TLS certificates from Let's Encrypt.
Insert the following configuration in `/etc/acme-client.conf` and replace `example.tld` with your domain:
```
#
# $OpenBSD: acme-client.conf,v 1.5 2023/05/10 07:34:57 tb Exp $
#
authority letsencrypt {
api url "https://acme-v02.api.letsencrypt.org/directory"
account key "/etc/acme/letsencrypt-privkey.pem"
}
domain example.tld {
# Adds alternative names to the certificate. Useful when serving media on another domain. Comma or space separated list.
# alternative names { }
domain key "/etc/ssl/private/example.tld.key"
domain certificate "/etc/ssl/example.tld_cert-only.crt"
domain full chain certificate "/etc/ssl/example.tld.crt"
sign with letsencrypt
}
```
Check the configuration:
```
# acme-client -n
```
### Configuring the Web server
Pleroma supports two Web servers:
* nginx (recommended for most users)
* OpenBSD's httpd and relayd (ONLY for advanced users, media proxy cache is NOT supported and will NOT work properly)
#### nginx
Since nginx is not installed by default, install it by running:
```
# pkg_add nginx
```
Add the following to `/etc/nginx/nginx.conf`, within the `server {}` block listening on port 80 and change `server_name`, as follows:
```
http {
...
server {
...
server_name localhost; # Replace with your domain
location /.well-known/acme-challenge {
rewrite ^/\.well-known/acme-challenge/(.*) /$1 break;
root /var/www/acme;
}
}
}
```
Start the nginx service and acquire certificates:
```
# rcctl start nginx
# acme-client example.tld
```
Add certificate auto-renewal by adding acme-client to `/etc/weekly.local`, replace `example.tld` with your domain:
```
# echo "acme-client example.tld && rcctl reload nginx" >> /etc/weekly.local
```
OpenBSD's default nginx configuration does not contain an include directive, which is typically used for multiple sites.
Therefore, you will need to first create the required directory as follows:
```
# mkdir /etc/nginx/sites-available
# mkdir /etc/nginx/sites-enabled
```
Next add the `include` directive to `/etc/nginx/nginx.conf`, within the `http {}` block, as follows:
```
http {
...
server {
...
}
include /etc/nginx/sites-enabled/*;
}
```
As root, copy `/home/_pleroma/pleroma/installation/pleroma.nginx` to `/etc/nginx/sites-available/pleroma.nginx`.
Edit default `/etc/nginx/sites-available/pleroma.nginx` settings and replace `example.tld` with your domain:
* Uncomment the location block for `~ /\.well-known/acme-challenge` in the server block listening on port 80
- add `rewrite ^/\.well-known/acme-challenge/(.*) /$1 break;` above the `root` location
- change the `root` location to `/var/www/acme;`
* Change `ssl_trusted_certificate` to `/etc/ssl/example.tld_cert-only.crt`
* Change `ssl_certificate` to `/etc/ssl/example.tld.crt`
* Change `ssl_certificate_key` to `/etc/ssl/private/example.tld.key`
Remove the following `location {}` block from `/etc/nginx/nginx.conf`, that was previously added for acquiring certificates and change `server_name` back to `localhost`:
```
http {
...
server {
...
server_name example.tld; # Change back to localhost
# Delete this block
location /.well-known/acme-challenge {
rewrite ^/\.well-known/acme-challenge/(.*) /$1 break;
root /var/www/acme;
}
}
}
```
Symlink the Pleroma configuration to the enabled sites:
```
# ln -s /etc/nginx/sites-available/pleroma.nginx /etc/nginx/sites-enabled
```
Check nginx configuration syntax by running:
```
# nginx -t
```
Note: If the above command complains about a `conflicting server name`, check again that the `location {}` block for acquiring certificates has been removed from `/etc/nginx/nginx.conf` and that the `server_name` has been reverted back to `localhost`.
After doing so run `# nginx -t` again.
If the configuration is correct, you can now enable and reload the nginx service:
```
# rcctl enable nginx
# rcctl reload nginx
```
#### httpd
***Skip this section when using nginx***
httpd will have two functions:
* redirect requests trying to reach the instance over http to the https URL
* get Let's Encrypt certificates, with acme-client
As root, copy `/home/_pleroma/pleroma/installation/openbsd/httpd.conf` to `/etc/httpd.conf`, or modify the existing one.
Edit `/etc/httpd.conf` settings and change:
* `<ipaddr>` with your instance's IPv4 address
* All occurrences of `example.tld` with your instance's domain name
* When using IPv6 also change:
- Uncomment the `ext_inet6="<ip6addr>"` line near the beginning of the file and change `<ip6addr` to your instance's IPv6 address
- Uncomment the line starting with `listen on $ext_inet6` in the `server` block
Check the configuration by running:
```
# httpd -n
```
If the configuration is correct, enable and start the `httpd` service:
```
# rcctl enable httpd
# rcctl start httpd
```
Acquire certificate:
```
# acme-client example.tld
```
#### relayd
***Skip this section when using nginx***
relayd will be used as the reverse proxy sitting in front of pleroma.
As root, copy `/home/_pleroma/pleroma/installation/openbsd/relayd.conf` to `/etc/relayd.conf`, or modify the existing one.
Edit `/etc/relayd.conf` settings and change:
* `<ipaddr>` with your instance's IPv4 address
* All occurrences of `example.tld` with your instance's domain name
* When using IPv6 also change:
- Uncomment the `ext_inet6="<ip6addr>"` line near the beginning of the file and change `<ip6addr>` to your instance's IPv6 address
- Uncomment the line starting with `listen on $ext_inet6` in the `relay wwwtls` block
Check the configuration by running:
```
# relayd -n
```
If the configuration is correct, enable and start the `relayd` service:
```
# rcctl enable relayd
# rcctl start relayd
```
Add certificate auto-renewal by adding acme-client to `/etc/weekly.local`, replace `example.tld` with your domain:
```
# echo "acme-client example.tld && rcctl reload relayd" >> /etc/weekly.local
```
#### (Strongly recommended) serve media on another domain
Refer to the [Hardening your instance](../configuration/hardening.md) document on how to serve media on another domain. We STRONGLY RECOMMEND you to do this to minimize attack vectors.
### Starting pleroma at boot
Copy the startup script and make sure it's executable:
```
# cp /home/_pleroma/pleroma/installation/openbsd/rc.d/pleroma /etc/rc.d/pleroma
# chmod 555 /etc/rc.d/pleroma
```
Enable and start the pleroma service:
```
# rcctl enable pleroma
# rcctl start pleroma
```
### Create administrative user
If your instance is up and running, you can create your first user with administrative rights with the following commands as the \_pleroma user:
```
$ cd pleroma
$ MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress> --admin
```
### Further reading
{! backend/installation/further_reading.include !}
## Questions
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
diff --git a/docs/installation/openbsd_fi.md b/docs/installation/openbsd_fi.md
index 858e64020..eda75fa5c 100644
--- a/docs/installation/openbsd_fi.md
+++ b/docs/installation/openbsd_fi.md
@@ -1,123 +1,123 @@
# Pleroman asennus OpenBSD:llä
Note: This article is potentially outdated because at this time we may not have people who can speak this language well enough to update it. To see the up-to-date version, which may have significant differences or important caveats of the installation process, look up the English version.
Tarvitset:
* Oman domainin
* OpenBSD 7.5 -serverin
* Auttavan ymmärryksen unix-järjestelmistä
Komennot, joiden edessä on '#', tulee ajaa käyttäjänä `root`. Tämä on
suositeltavaa tehdä komennon `doas` avulla, katso `doas (1)` ja `doas.conf (5)`.
Tästä eteenpäin oletuksena on, että domain "esimerkki.com" osoittaa
serverin IP-osoitteeseen.
Jos asennuksen kanssa on ongelmia, IRC-kanava #pleroma Libera.chat tai
Matrix-kanava #pleroma:libera.chat ovat hyviä paikkoja löytää apua
(englanniksi), `/msg eal kukkuu` jos haluat välttämättä puhua härmää.
Asenna tarvittava ohjelmisto:
`# pkg_add git elixir gmake postgresql-server postgresql-contrib cmake libmagic libvips`
#### Optional software
[`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md):
* ImageMagick
* ffmpeg
- * exiftool
Asenna tarvittava ohjelmisto:
-`# pkg_add ImageMagick ffmpeg p5-Image-ExifTool`
+`# pkg_add ImageMagick ffmpeg`
+
Luo postgresql-tietokanta:
`# su - _postgresql`
`$ mkdir /var/postgresql/data`
`$ initdb -D /var/postgresql/data -E UTF8`
`$ createdb`
Käynnistä tietokanta ja aseta se käynnistymään automaattisesti.
`# rcctl start postgresql`
`# rcctl enable postgresql`
Luo käyttäjä pleromaa varten (kysyy muutaman kysymyksen):
`# adduser pleroma`
Vaihda pleroma-käyttäjään ja mene kotihakemistoosi:
`# su - pleroma`
Lataa pleroman lähdekoodi:
`$ git clone -b stable https://git.pleroma.social/pleroma/pleroma.git`
`$ cd pleroma`
Asenna tarvittavat elixir-kirjastot:
`$ mix deps.get`
`$ mix deps.compile`
Luo tarvittava konfiguraatio:
`$ mix generate_config`
`$ cp config/generated_config.exs config/prod.secret.exs`
Aja luodut tietokantakomennot:
`# su _postgres -c 'psql -f config/setup_db.psql'`
`$ MIX_ENV=prod mix ecto.migrate`
Käynnistä pleroma-prosessi:
`$ MIX_ENV=prod mix compile`
`$ MIX_ENV=prod mix phx.server`
Tässä vaiheessa on hyvä tarkistaa että asetukset ovat oikein. Avaa selaimella,
curlilla tai vastaavalla työkalulla `esimerkki.com:4000/api/v1/instance` ja katso
että kohta "uri" on "https://esimerkki.com".
Huom! Muista varmistaa että muuttuja MIX_ENV on "prod" mix-komentoja ajaessasi.
Mix lukee oikean konfiguraatiotiedoston sen mukaisesti.
Ohessa enimmäkseen toimivaksi todettu rc.d-skripti pleroman käynnistämiseen.
Kirjoita se tiedostoon /etc/rc.d/pleroma. Tämän jälkeen aja
`# chmod +x /etc/rc.d/pleroma`, ja voit käynnistää pleroman komennolla
`# /etc/rc.d/pleroma start`.
```
#!/bin/ksh
#/etc/rc.d/pleroma
daemon="cd /home/pleroma/pleroma;MIX_ENV=prod /usr/local/bin/elixir"
daemon_flags="--detached /usr/local/bin/mix phx.server"
daemon_user="pleroma"
rc_reload="NO"
rc_bg="YES"
pexp="beam"
. /etc/rc.d/rc.subr
rc_cmd $1
```
Tämän jälkeen tarvitset enää HTTP-serverin välittämään kutsut pleroma-prosessille.
Tiedostosta `install/pleroma.nginx` löytyy esimerkkikonfiguraatio, ja TLS-sertifikaatit
saat ilmaiseksi esimerkiksi [letsencryptiltä](https://certbot.eff.org/lets-encrypt/opbsd-nginx.html).
Nginx asentuu yksinkertaisesti komennolla `# pkg_add nginx`.
Kun olet valmis, avaa https://esimerkki.com selaimessasi. Luo käyttäjä ja seuraa kiinnostavia
tyyppejä muilla palvelimilla!
diff --git a/docs/installation/optional/media_graphics_packages.md b/docs/installation/optional/media_graphics_packages.md
index 89125d1c6..e7e6283f8 100644
--- a/docs/installation/optional/media_graphics_packages.md
+++ b/docs/installation/optional/media_graphics_packages.md
@@ -1,37 +1,27 @@
# Optional software packages needed for specific functionality
For specific Pleroma functionality (which is disabled by default) some or all of the below packages are required:
-* `ImageMagic`
+* `ImageMagick`
* `ffmpeg`
-* `exiftool`
Please refer to documentation in `docs/installation` on how to install them on specific OS.
Note: the packages are not required with the current default settings of Pleroma.
## `ImageMagick`
`ImageMagick` is a set of tools to create, edit, compose, or convert bitmap images.
It is required for the following Pleroma features:
* `Pleroma.Upload.Filters.Mogrify`, `Pleroma.Upload.Filters.Mogrifun` upload filters (related config: `Pleroma.Upload/filters` in `config/config.exs`)
* Media preview proxy for still images (related config: `media_preview_proxy/enabled` in `config/config.exs`)
## `ffmpeg`
`ffmpeg` is software to record, convert and stream audio and video.
It is required for the following Pleroma features:
* Media preview proxy for videos (related config: `media_preview_proxy/enabled` in `config/config.exs`)
-
-## `exiftool`
-
-`exiftool` is media files metadata reader/writer.
-
-It is required for the following Pleroma features:
-
-* `Pleroma.Upload.Filters.Exiftool.StripLocation` upload filter (related config: `Pleroma.Upload/filters` in `config/config.exs`)
-* `Pleroma.Upload.Filters.Exiftool.ReadDescription` upload filter (related config: `Pleroma.Upload/filters` in `config/config.exs`)
diff --git a/docs/installation/otp_en.md b/docs/installation/otp_en.md
index 123dccf3a..2ff64b77e 100644
--- a/docs/installation/otp_en.md
+++ b/docs/installation/otp_en.md
@@ -1,315 +1,314 @@
# Installing on Linux using OTP releases
{! backend/installation/otp_vs_from_source.include !}
This guide covers a installation using OTP releases as built by the Pleroma project, it is meant as a fallback to distribution packages/recipes which are the preferred installation method.
To install Pleroma from source, please check out the corresponding guide for your distro.
## Pre-requisites
* A machine you have root access to running Debian GNU/Linux or compatible (eg. Ubuntu), or Alpine on `x86_64`, `aarch64` or `armv7l` CPU. If you are not sure what you are running see [Detecting flavour section](#detecting-flavour) below
* A (sub)domain pointed to the machine
You will be running commands as root. If you aren't root already, please elevate your privileges by executing `sudo -i`/`su`.
Similarly to other binaries, OTP releases tend to be only compatible with the distro they are built on, as such this guide focuses only on Debian/Ubuntu and Alpine.
!!! note
If you get `GLIBC_... not found` errors on Debian/Ubuntu, you can run the OTP release from `/opt/pleroma` inside a newer distro container without upgrading the host. See [`release_to_docker_en.md`](release_to_docker_en.md).
### Detecting flavour
Paste the following into the shell:
```sh
arch="$(uname -m)";if [ "$arch" = "x86_64" ];then arch="amd64";elif [ "$arch" = "armv7l" ];then arch="arm";elif [ "$arch" = "aarch64" ];then arch="arm64";else echo "Unsupported arch: $arch">&2;fi;if getconf GNU_LIBC_VERSION>/dev/null;then libc_postfix="";elif [ "$(ldd 2>&1|head -c 9)" = "musl libc" ];then libc_postfix="-musl";elif [ "$(find /lib/libc.musl*|wc -l)" ];then libc_postfix="-musl";else echo "Unsupported libc">&2;fi;echo "$arch$libc_postfix"
```
This should give your flavour string. If not this just means that we don't build releases for your platform, you can still try installing from source.
### Installing the required packages
Other than things bundled in the OTP release Pleroma depends on:
* curl (to download the release build)
* unzip (needed to unpack release builds)
* ncurses (ERTS won't run without it)
* PostgreSQL (also utilizes extensions in postgresql-contrib)
* nginx (could be swapped with another reverse proxy but this guide covers only it)
* certbot (for Let's Encrypt certificates, could be swapped with another ACME client, but this guide covers only it)
* libmagic/file
=== "Alpine"
```
awk 'NR==2' /etc/apk/repositories | sed 's/main/community/' | tee -a /etc/apk/repositories
apk update
apk add curl unzip ncurses postgresql postgresql-contrib nginx certbot file-dev
```
=== "Debian/Ubuntu"
```
apt install curl unzip libncurses5 postgresql postgresql-contrib nginx certbot libmagic-dev
```
### Installing optional packages
Per [`docs/installation/optional/media_graphics_packages.md`](optional/media_graphics_packages.md):
* ImageMagick
* ffmpeg
- * exiftool
=== "Alpine"
```
apk update
- apk add imagemagick ffmpeg exiftool
+ apk add imagemagick ffmpeg
```
=== "Debian/Ubuntu"
```
- apt install imagemagick ffmpeg libimage-exiftool-perl
+ apt install imagemagick ffmpeg
```
## Setup
### Configuring PostgreSQL
#### (Optional) Installing RUM indexes
!!! warning
It is recommended to use PostgreSQL v11 or newer. We have seen some minor issues with lower PostgreSQL versions.
RUM indexes are an alternative indexing scheme that is not included in PostgreSQL by default. You can read more about them on the [Configuration page](../configuration/cheatsheet.md#rum-indexing-for-full-text-search). They are completely optional and most of the time are not worth it, especially if you are running a single user instance (unless you absolutely need ordered search results).
=== "Alpine"
```
apk add git build-base postgresql-dev
git clone https://github.com/postgrespro/rum /tmp/rum
cd /tmp/rum
make USE_PGXS=1
make USE_PGXS=1 install
cd
rm -r /tmp/rum
```
=== "Debian/Ubuntu"
```
# Available only on Buster/19.04
apt install postgresql-11-rum
```
#### (Optional) Performance configuration
It is encouraged to check [Optimizing your PostgreSQL performance](../configuration/postgresql.md) document, for tips on PostgreSQL tuning.
Restart PostgreSQL to apply configuration changes:
=== "Alpine"
```
rc-service postgresql restart
```
=== "Debian/Ubuntu"
```
systemctl restart postgresql
```
### Installing Pleroma
```sh
# Create a Pleroma user
adduser --system --shell /bin/false --home /opt/pleroma pleroma
# Set the flavour environment variable to the string you got in Detecting flavour section.
# For example if the flavour is `amd64-musl` the command will be
export FLAVOUR="amd64-musl"
# Clone the release build into a temporary directory and unpack it
sudo -Hu pleroma "
curl 'https://git.pleroma.social/api/v4/projects/2/jobs/artifacts/stable/download?job=$FLAVOUR' -o /tmp/pleroma.zip
unzip /tmp/pleroma.zip -d /tmp/
"
# Move the release to the home directory and delete temporary files
sudo -Hu pleroma "
mv /tmp/release/* /opt/pleroma
rmdir /tmp/release
rm /tmp/pleroma.zip
"
# Create uploads directory and set proper permissions (skip if planning to use a remote uploader)
# Note: It does not have to be `/var/lib/pleroma/uploads`, the config generator will ask about the upload directory later
mkdir -p /var/lib/pleroma/uploads
chown -R pleroma /var/lib/pleroma
# Create custom public files directory (custom emojis, frontend bundle overrides, robots.txt, etc.)
# Note: It does not have to be `/var/lib/pleroma/static`, the config generator will ask about the custom public files directory later
mkdir -p /var/lib/pleroma/static
chown -R pleroma /var/lib/pleroma
# Create a config directory
mkdir -p /etc/pleroma
chown -R pleroma /etc/pleroma
# Run the config generator
sudo -Hu pleroma "./bin/pleroma_ctl instance gen --output /etc/pleroma/config.exs --output-psql /tmp/setup_db.psql"
# Create the postgres database
sudo -u postgres -s $SHELL -lc "psql -f /tmp/setup_db.psql"
# Create the database schema
sudo -Hu pleroma "./bin/pleroma_ctl migrate"
# If you have installed RUM indexes uncommend and run
# sudo -Hu pleroma "./bin/pleroma_ctl migrate --migrations-path priv/repo/optional_migrations/rum_indexing/"
# Start the instance to verify that everything is working as expected
sudo -Hu pleroma "./bin/pleroma daemon"
# Wait for about 20 seconds and query the instance endpoint, if it shows your uri, name and email correctly, you are configured correctly
sleep 20 && curl http://localhost:4000/api/v1/instance
# Stop the instance
sudo -Hu pleroma "./bin/pleroma stop"
```
### Setting up nginx and getting Let's Encrypt SSL certificaties
#### Get a Let's Encrypt certificate
```sh
certbot certonly --standalone --preferred-challenges http -d yourinstance.tld
```
#### Copy Pleroma nginx configuration to the nginx folder
The location of nginx configs is dependent on the distro
=== "Alpine"
```
cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/conf.d/pleroma.conf
```
=== "Debian/Ubuntu"
```
cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/sites-available/pleroma.conf
ln -s /etc/nginx/sites-available/pleroma.conf /etc/nginx/sites-enabled/pleroma.conf
```
If your distro does not have either of those you can append `include /etc/nginx/pleroma.conf` to the end of the http section in /etc/nginx/nginx.conf and
```sh
cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/pleroma.conf
```
#### Edit the nginx config
```sh
# Replace example.tld with your (sub)domain
$EDITOR path-to-nginx-config
# Verify that the config is valid
nginx -t
```
#### (Strongly recommended) serve media on another domain
Refer to the [Hardening your instance](../configuration/hardening.md) document on how to serve media on another domain. We STRONGLY RECOMMEND you to do this to minimize attack vectors.
#### Start nginx
=== "Alpine"
```
rc-service nginx start
```
=== "Debian/Ubuntu"
```
systemctl start nginx
```
At this point if you open your (sub)domain in a browser you should see a 502 error, that's because Pleroma is not started yet.
### Setting up a system service
=== "Alpine"
```
# Copy the service into a proper directory
cp /opt/pleroma/installation/init.d/pleroma /etc/init.d/pleroma
# Start pleroma and enable it on boot
rc-service pleroma start
rc-update add pleroma
```
=== "Debian/Ubuntu"
```
# Copy the service into a proper directory
cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service
# Start pleroma and enable it on boot
systemctl start pleroma
systemctl enable pleroma
```
If everything worked, you should see Pleroma-FE when visiting your domain. If that didn't happen, try reviewing the installation steps, starting Pleroma in the foreground and seeing if there are any errors.
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC, you can also [file an issue on our Gitlab](https://git.pleroma.social/pleroma/pleroma-support/issues/new).
## Post installation
### Setting up auto-renew of the Let's Encrypt certificate
```sh
# Create the directory for webroot challenges
mkdir -p /var/lib/letsencrypt
# Uncomment the webroot method
$EDITOR path-to-nginx-config
# Verify that the config is valid
nginx -t
```
=== "Alpine"
```
# Restart nginx
rc-service nginx restart
# Start the cron daemon and make it start on boot
rc-service crond start
rc-update add crond
# Ensure the webroot menthod and post hook is working
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --dry-run --post-hook 'rc-service nginx reload'
# Add it to the daily cron
echo '#!/bin/sh
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --post-hook "rc-service nginx reload"
' > /etc/periodic/daily/renew-pleroma-cert
chmod +x /etc/periodic/daily/renew-pleroma-cert
# If everything worked the output should contain /etc/cron.daily/renew-pleroma-cert
run-parts --test /etc/periodic/daily
```
=== "Debian/Ubuntu"
```
# Restart nginx
systemctl restart nginx
# Ensure the webroot menthod and post hook is working
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --dry-run --post-hook 'systemctl reload nginx'
# Add it to the daily cron
echo '#!/bin/sh
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --post-hook "systemctl reload nginx"
' > /etc/cron.daily/renew-pleroma-cert
chmod +x /etc/cron.daily/renew-pleroma-cert
# If everything worked the output should contain /etc/cron.daily/renew-pleroma-cert
run-parts --test /etc/cron.daily
```
## Create your first user and set as admin
```sh
cd /opt/pleroma
su pleroma -s $SHELL -lc "./bin/pleroma_ctl user new joeuser joeuser@sld.tld --admin"
```
This will create an account withe the username of 'joeuser' with the email address of joeuser@sld.tld, and set that user's account as an admin. This will result in a link that you can paste into the browser, which logs you in and enables you to set the password.
## Further reading
{! backend/installation/further_reading.include !}
## Questions
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC, you can also [file an issue on our Gitlab](https://git.pleroma.social/pleroma/pleroma-support/issues/new).
diff --git a/installation/release-to-docker/Dockerfile b/installation/release-to-docker/Dockerfile
index 0ffeb5d5b..ac9118aba 100644
--- a/installation/release-to-docker/Dockerfile
+++ b/installation/release-to-docker/Dockerfile
@@ -1,26 +1,26 @@
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
LC_ALL=C.UTF-8
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
gosu \
libstdc++6 \
libncurses6 libncursesw6 \
openssl libssl3 \
libmagic1t64 file \
postgresql-client \
- ffmpeg imagemagick libimage-exiftool-perl \
+ ffmpeg imagemagick \
libvips42t64 \
unzip \
curl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /opt/pleroma
COPY pleroma-host-release-entrypoint.sh /usr/local/bin/pleroma-host-release-entrypoint.sh
RUN chmod +x /usr/local/bin/pleroma-host-release-entrypoint.sh
ENTRYPOINT ["/usr/local/bin/pleroma-host-release-entrypoint.sh"]
CMD ["/opt/pleroma/bin/pleroma", "start"]
diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex
index 143af5cdd..5753d4f36 100644
--- a/lib/mix/tasks/pleroma/instance.ex
+++ b/lib/mix/tasks/pleroma/instance.ex
@@ -1,355 +1,337 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Instance do
use Mix.Task
import Mix.Pleroma
alias Pleroma.Config
@shortdoc "Manages Pleroma instance"
@moduledoc File.read!("docs/administration/CLI_tasks/instance.md")
def run(["gen" | rest]) do
{options, [], []} =
OptionParser.parse(
rest,
strict: [
force: :boolean,
output: :string,
output_psql: :string,
domain: :string,
instance_name: :string,
admin_email: :string,
notify_email: :string,
dbhost: :string,
dbname: :string,
dbuser: :string,
dbpass: :string,
rum: :string,
indexable: :string,
db_configurable: :string,
uploads_dir: :string,
static_dir: :string,
listen_ip: :string,
listen_port: :string,
strip_uploads_location: :string,
read_uploads_description: :string,
anonymize_uploads: :string,
dedupe_uploads: :string
],
aliases: [
o: :output,
f: :force
]
)
paths =
[config_path, psql_path] = [
Keyword.get(options, :output, "config/generated_config.exs"),
Keyword.get(options, :output_psql, "config/setup_db.psql")
]
will_overwrite = Enum.filter(paths, &File.exists?/1)
proceed? = Enum.empty?(will_overwrite) or Keyword.get(options, :force, false)
if proceed? do
[domain, port | _] =
String.split(
get_option(
options,
:domain,
"What domain will your instance use? (e.g pleroma.soykaf.com)"
),
":"
) ++ [443]
name =
get_option(
options,
:instance_name,
"What is the name of your instance? (e.g. The Corndog Emporium)",
domain
)
email = get_option(options, :admin_email, "What is your admin email address?")
notify_email =
get_option(
options,
:notify_email,
"What email address do you want to use for sending email notifications?",
email
)
indexable =
get_option(
options,
:indexable,
"Do you want search engines to index your site? (y/n)",
"y"
) === "y"
db_configurable? =
get_option(
options,
:db_configurable,
"Do you want to store the configuration in the database (allows controlling it from admin-fe)? (y/n)",
"n"
) === "y"
dbhost = get_option(options, :dbhost, "What is the hostname of your database?", "localhost")
dbname = get_option(options, :dbname, "What is the name of your database?", "pleroma")
dbuser =
get_option(
options,
:dbuser,
"What is the user used to connect to your database?",
"pleroma"
)
dbpass =
get_option(
options,
:dbpass,
"What is the password used to connect to your database?",
:crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64),
"autogenerated"
)
rum_enabled =
get_option(
options,
:rum,
"Would you like to use RUM indices?",
"n"
) === "y"
listen_port =
get_option(
options,
:listen_port,
"What port will the app listen to (leave it if you are using the default setup with nginx)?",
4000
)
listen_ip =
get_option(
options,
:listen_ip,
"What ip will the app listen to (leave it if you are using the default setup with nginx)?",
"127.0.0.1"
)
uploads_dir =
get_option(
options,
:uploads_dir,
"What directory should media uploads go in (when using the local uploader)?",
Config.get([Pleroma.Uploaders.Local, :uploads])
)
|> Path.expand()
static_dir =
get_option(
options,
:static_dir,
"What directory should custom public files be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)?",
Config.get([:instance, :static_dir])
)
|> Path.expand()
- {strip_uploads_location_message, strip_uploads_location_default} =
- if Pleroma.Utils.command_available?("exiftool") do
- {"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as installed. (y/n)",
- "y"}
- else
- {"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as not installed, please install it if you answer yes. (y/n)",
- "n"}
- end
-
strip_uploads_location =
get_option(
options,
:strip_uploads_location,
- strip_uploads_location_message,
- strip_uploads_location_default
+ "Do you want to strip location (GPS) data from uploaded images? (y/n)",
+ "y"
) === "y"
- {read_uploads_description_message, read_uploads_description_default} =
- if Pleroma.Utils.command_available?("exiftool") do
- {"Do you want to read data from uploaded files so clients can use it to prefill fields like image description? This requires exiftool, it was detected as installed. (y/n)",
- "y"}
- else
- {"Do you want to read data from uploaded files so clients can use it to prefill fields like image description? This requires exiftool, it was detected as not installed, please install it if you answer yes. (y/n)",
- "n"}
- end
-
read_uploads_description =
get_option(
options,
:read_uploads_description,
- read_uploads_description_message,
- read_uploads_description_default
+ "Do you want to read metadata from uploaded images so clients can prefill the media description field? (y/n)",
+ "y"
) === "y"
anonymize_uploads =
get_option(
options,
:anonymize_uploads,
"Do you want to anonymize the filenames of uploads? (y/n)",
"n"
) === "y"
dedupe_uploads =
get_option(
options,
:dedupe_uploads,
"Do you want to deduplicate uploaded files? (y/n)",
"n"
) === "y"
Config.put([:instance, :static_dir], static_dir)
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
jwt_secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
lv_signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
{web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1)
template_dir = Application.app_dir(:pleroma, "priv") <> "/templates"
result_config =
EEx.eval_file(
template_dir <> "/sample_config.eex",
domain: domain,
port: port,
email: email,
notify_email: notify_email,
name: name,
dbhost: dbhost,
dbname: dbname,
dbuser: dbuser,
dbpass: dbpass,
secret: secret,
jwt_secret: jwt_secret,
signing_salt: signing_salt,
lv_signing_salt: lv_signing_salt,
web_push_public_key: Base.url_encode64(web_push_public_key, padding: false),
web_push_private_key: Base.url_encode64(web_push_private_key, padding: false),
db_configurable?: db_configurable?,
static_dir: static_dir,
uploads_dir: uploads_dir,
rum_enabled: rum_enabled,
listen_ip: listen_ip,
listen_port: listen_port,
upload_filters:
upload_filters(%{
strip_location: strip_uploads_location,
read_description: read_uploads_description,
anonymize: anonymize_uploads,
dedupe: dedupe_uploads
})
)
result_psql =
EEx.eval_file(
template_dir <> "/sample_psql.eex",
dbname: dbname,
dbuser: dbuser,
dbpass: dbpass,
rum_enabled: rum_enabled
)
config_dir = Path.dirname(config_path)
psql_dir = Path.dirname(psql_path)
# Note: Distros requiring group read (0o750) on those directories should
# pre-create the directories.
[config_dir, psql_dir, static_dir, uploads_dir]
|> Enum.reject(&File.exists?/1)
|> Enum.each(fn dir ->
Pleroma.Backports.mkdir_p!(dir)
File.chmod!(dir, 0o700)
end)
shell_info("Writing config to #{config_path}.")
# Sadly no fchmod(2) equivalent in Elixir…
File.touch!(config_path)
File.chmod!(config_path, 0o640)
File.write(config_path, result_config)
shell_info("Writing the postgres script to #{psql_path}.")
File.write(psql_path, result_psql)
write_robots_txt(static_dir, indexable, template_dir)
shell_info(
"\n All files successfully written! Refer to the installation instructions for your platform for next steps."
)
if db_configurable? do
shell_info(
" Please transfer your config to the database after running database migrations. Refer to \"Transferring the config to/from the database\" section of the docs for more information."
)
end
else
shell_error(
"The task would have overwritten the following files:\n" <>
Enum.map_join(will_overwrite, &"- #{&1}\n") <> "Rerun with `--force` to overwrite them."
)
end
end
defp write_robots_txt(static_dir, indexable, template_dir) do
robots_txt =
EEx.eval_file(
template_dir <> "/robots_txt.eex",
indexable: indexable
)
robots_txt_path = Path.join(static_dir, "robots.txt")
if File.exists?(robots_txt_path) do
File.cp!(robots_txt_path, "#{robots_txt_path}.bak")
shell_info("Backing up existing robots.txt to #{robots_txt_path}.bak")
end
File.write(robots_txt_path, robots_txt)
shell_info("Writing #{robots_txt_path}.")
end
defp upload_filters(filters) when is_map(filters) do
enabled_filters =
if filters.strip_location do
[Pleroma.Upload.Filter.Exiftool.StripLocation]
else
[]
end
enabled_filters =
if filters.read_description do
enabled_filters ++ [Pleroma.Upload.Filter.Exiftool.ReadDescription]
else
enabled_filters
end
enabled_filters =
if filters.anonymize do
enabled_filters ++ [Pleroma.Upload.Filter.AnonymizeFilename]
else
enabled_filters
end
enabled_filters =
if filters.dedupe do
enabled_filters ++ [Pleroma.Upload.Filter.Dedupe]
else
enabled_filters
end
enabled_filters
end
end
diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex
index 87ecb7e2d..d9f4eb1d0 100644
--- a/lib/pleroma/application_requirements.ex
+++ b/lib/pleroma/application_requirements.ex
@@ -1,291 +1,289 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ApplicationRequirements do
@moduledoc """
The module represents the collection of validations to runs before start server.
"""
defmodule VerifyError do
defexception([:message])
@type t :: %__MODULE__{}
end
alias Pleroma.Config
alias Pleroma.Helpers.MediaHelper
import Ecto.Query
require Logger
@spec verify!() :: :ok | VerifyError.t()
def verify! do
:ok
|> check_system_commands!()
|> check_confirmation_accounts!()
|> check_migrations_applied!()
|> check_welcome_message_config!()
|> check_rum!()
|> check_repo_pool_size!()
|> check_mrfs()
|> handle_result()
end
defp handle_result(:ok), do: :ok
defp handle_result({:error, message}), do: raise(VerifyError, message: message)
defp check_welcome_message_config!(:ok) do
if Pleroma.Config.get([:welcome, :email, :enabled], false) and
not Pleroma.Emails.Mailer.enabled?() do
Logger.warning("""
To send welcome emails, you need to enable the mailer.
Welcome emails will NOT be sent with the current config.
Enable the mailer:
config :pleroma, Pleroma.Emails.Mailer, enabled: true
""")
end
:ok
end
defp check_welcome_message_config!(result), do: result
# Checks account confirmation email
#
def check_confirmation_accounts!(:ok) do
if Pleroma.Config.get([:instance, :account_activation_required]) &&
not Pleroma.Emails.Mailer.enabled?() do
Logger.warning("""
Account activation is required, but the mailer is disabled.
Users will NOT be able to confirm their accounts with this config.
Either disable account activation or enable the mailer.
Disable account activation:
config :pleroma, :instance, account_activation_required: false
Enable the mailer:
config :pleroma, Pleroma.Emails.Mailer, enabled: true
""")
end
:ok
end
def check_confirmation_accounts!(result), do: result
# Checks for pending migrations.
#
def check_migrations_applied!(:ok) do
unless Pleroma.Config.get(
[:i_am_aware_this_may_cause_data_loss, :disable_migration_check],
false
) do
{_, res, _} =
Ecto.Migrator.with_repo(Pleroma.Repo, fn repo ->
down_migrations =
Ecto.Migrator.migrations(repo)
|> Enum.reject(fn
{:up, _, _} -> true
{:down, _, _} -> false
end)
if length(down_migrations) > 0 do
down_migrations_text =
Enum.map(down_migrations, fn {:down, id, name} -> "- #{name} (#{id})\n" end)
Logger.error(
"The following migrations were not applied:\n#{down_migrations_text}" <>
"If you want to start Pleroma anyway, set\n" <>
"config :pleroma, :i_am_aware_this_may_cause_data_loss, disable_migration_check: true"
)
{:error, "Unapplied Migrations detected"}
else
:ok
end
end)
res
else
:ok
end
end
def check_migrations_applied!(result), do: result
# Checks for settings of RUM indexes.
#
defp check_rum!(:ok) do
{_, res, _} =
Ecto.Migrator.with_repo(Pleroma.Repo, fn repo ->
migrate =
from(o in "columns",
where: o.table_name == "objects",
where: o.column_name == "fts_content"
)
|> repo.exists?(prefix: "information_schema")
setting = Pleroma.Config.get([:database, :rum_enabled], false)
do_check_rum!(setting, migrate)
end)
res
end
defp check_rum!(result), do: result
defp do_check_rum!(setting, migrate) do
case {setting, migrate} do
{true, false} ->
Logger.error(
"Use `RUM` index is enabled, but were not applied migrations for it.\n" <>
"If you want to start Pleroma anyway, set\n" <>
"config :pleroma, :database, rum_enabled: false\n" <>
"Otherwise apply the following migrations:\n" <>
"`mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/`"
)
{:error, "Unapplied RUM Migrations detected"}
{false, true} ->
Logger.error(
"Detected applied migrations to use `RUM` index, but `RUM` isn't enable in settings.\n" <>
"If you want to use `RUM`, set\n" <>
"config :pleroma, :database, rum_enabled: true\n" <>
"Otherwise roll `RUM` migrations back.\n" <>
"`mix ecto.rollback --migrations-path priv/repo/optional_migrations/rum_indexing/`"
)
{:error, "RUM Migrations detected"}
_ ->
:ok
end
end
defp check_system_commands!(:ok) do
filter_commands_statuses = [
- check_filter(Pleroma.Upload.Filter.Exiftool.StripLocation, "exiftool"),
- check_filter(Pleroma.Upload.Filter.Exiftool.ReadDescription, "exiftool"),
check_filter(Pleroma.Upload.Filter.Mogrify, "mogrify"),
check_filter(Pleroma.Upload.Filter.Mogrifun, "mogrify"),
check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "ffprobe")
]
preview_proxy_commands_status =
if !Config.get([:media_preview_proxy, :enabled]) or
MediaHelper.missing_dependencies() == [] do
true
else
Logger.error(
"The following dependencies required by Media preview proxy " <>
"(which is currently enabled) are not installed: " <>
inspect(MediaHelper.missing_dependencies())
)
false
end
language_detector_commands_status =
if Pleroma.Language.LanguageDetector.missing_dependencies() == [] do
true
else
Logger.error(
"The following dependencies required by the currently enabled " <>
"language detection provider are not installed: " <>
inspect(Pleroma.Language.LanguageDetector.missing_dependencies())
)
false
end
translation_commands_status =
if Pleroma.Language.Translation.missing_dependencies() == [] do
true
else
Logger.error(
"The following dependencies required by the currently enabled " <>
"translation provider are not installed: " <>
inspect(Pleroma.Language.Translation.missing_dependencies())
)
false
end
if Enum.all?(
[
preview_proxy_commands_status,
language_detector_commands_status,
translation_commands_status | filter_commands_statuses
],
& &1
) do
:ok
else
{:error,
"System commands missing. Check logs and see `docs/installation` for more details."}
end
end
defp check_repo_pool_size!(:ok) do
if Pleroma.Config.get([Pleroma.Repo, :pool_size], 10) != 10 and
not Pleroma.Config.get([:dangerzone, :override_repo_pool_size], false) do
Logger.error("""
!!!CONFIG WARNING!!!
The database pool size has been altered from the recommended value of 10.
Please revert or ensure your database is tuned appropriately and then set
`config :pleroma, :dangerzone, override_repo_pool_size: true`.
If you are experiencing database timeouts, please check the "Optimizing
your PostgreSQL performance" section in the documentation. If you still
encounter issues after that, please open an issue on the tracker.
""")
{:error, "Repo.pool_size different than recommended value."}
else
:ok
end
end
defp check_repo_pool_size!(result), do: result
defp check_filter(filter, command_required) do
filters = Config.get([Pleroma.Upload, :filters])
if filter in filters and not Pleroma.Utils.command_available?(command_required) do
Logger.error(
"#{filter} is specified in list of Pleroma.Upload filters, but the " <>
"#{command_required} command is not found"
)
false
else
true
end
end
defp check_mrfs(:ok) do
mrfs = Config.get!([:mrf, :policies])
missing_mrfs =
Enum.reduce(mrfs, [], fn x, acc ->
case Code.ensure_compiled(x) do
{:module, _} -> acc
{:error, _} -> acc ++ [x]
end
end)
if Enum.empty?(missing_mrfs) do
:ok
else
{:error, "The following MRF modules are configured but missing: #{inspect(missing_mrfs)}"}
end
end
defp check_mrfs(result), do: result
end
diff --git a/lib/pleroma/upload/filter/exiftool/read_description.ex b/lib/pleroma/upload/filter/exiftool/read_description.ex
index 8283b8643..ac17a9856 100644
--- a/lib/pleroma/upload/filter/exiftool/read_description.ex
+++ b/lib/pleroma/upload/filter/exiftool/read_description.ex
@@ -1,54 +1,923 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Upload.Filter.Exiftool.ReadDescription do
@moduledoc """
Gets a valid description from the related EXIF tags and provides them in the response if no description is provided yet.
- It will first check ImageDescription, when that doesn't probide a valid description, it will check iptc:Caption-Abstract.
+ It will first check ImageDescription, when that doesn't provide a valid description, it will check iptc:Caption-Abstract.
A valid description means the fields are filled in and not too long (see `:instance, :description_limit`).
"""
@behaviour Pleroma.Upload.Filter
+ @image_description_exif_tag 0x010E
+
+ @iptc_marker 0x1C
+ @iptc_caption_record 0x02
+ @iptc_caption_dataset 0x78
+
+ @png_signature <<0x89, "PNG", 0x0D, 0x0A, 0x1A, 0x0A>>
+ @jpg_soi_marker <<0xFF, 0xD8>>
+ @webp_riff_marker "RIFF"
+ @gif87a_header "GIF87a"
+ @gif89a_header "GIF89a"
+
def filter(%Pleroma.Upload{description: description})
when is_binary(description),
do: {:ok, :noop}
def filter(%Pleroma.Upload{tempfile: file} = upload),
do: {:ok, :filtered, upload |> Map.put(:description, read_description_from_exif_data(file))}
def filter(_, _), do: {:ok, :noop}
defp read_description_from_exif_data(file) do
- nil
- |> read_when_empty(file, "-ImageDescription")
- |> read_when_empty(file, "-iptc:Caption-Abstract")
+ with true <- File.exists?(file),
+ {:ok, data} <- File.read(file) do
+ data
+ |> read_description_from_binary()
+ |> normalize_description()
+ else
+ _ -> nil
+ end
+ end
+
+ defp read_description_from_binary(<<@jpg_soi_marker, _rest::binary>> = data) do
+ image_description = data |> jpeg_image_description() |> normalize_description()
+
+ if is_binary(image_description) do
+ image_description
+ else
+ data |> jpeg_iptc_caption_abstract() |> normalize_description()
+ end
+ end
+
+ defp read_description_from_binary(<<@png_signature, _rest::binary>> = data) do
+ image_description = data |> png_image_description() |> normalize_description()
+
+ if is_binary(image_description) do
+ image_description
+ else
+ data |> png_iptc_caption_abstract() |> normalize_description()
+ end
+ end
+
+ defp read_description_from_binary(
+ <<@webp_riff_marker, _riff_size::32-little, "WEBP", _rest::binary>> = data
+ ) do
+ data
+ |> webp_image_description()
+ |> normalize_description()
+ end
+
+ defp read_description_from_binary(<<@gif87a_header, _rest::binary>> = data) do
+ data
+ |> gif_image_description()
+ |> normalize_description()
+ end
+
+ defp read_description_from_binary(<<@gif89a_header, _rest::binary>> = data) do
+ data
+ |> gif_image_description()
+ |> normalize_description()
+ end
+
+ defp read_description_from_binary(<<_size::32-big, "ftyp", _rest::binary>> = data) do
+ data
+ |> isobmff_image_description()
+ |> normalize_description()
+ end
+
+ defp read_description_from_binary(_), do: nil
+
+ defp normalize_description(nil), do: nil
+
+ defp normalize_description(description) when is_binary(description) do
+ description = to_utf8(description) |> String.trim()
+ description_limit = Pleroma.Config.get([:instance, :description_limit])
+
+ if description != "" and String.length(description) <= description_limit do
+ description
+ else
+ nil
+ end
end
- defp read_when_empty(current_description, _, _) when is_binary(current_description),
- do: current_description
+ defp to_utf8(data) when is_binary(data) do
+ if String.valid?(data) do
+ data
+ else
+ :unicode.characters_to_binary(data, :latin1)
+ end
+ end
- defp read_when_empty(_, file, tag) do
- if File.exists?(file) do
- try do
- {tag_content, 0} =
- System.cmd("exiftool", ["-m", "-b", "-s3", tag, file],
- stderr_to_stdout: false,
- parallelism: true
- )
+ defp jpeg_image_description(data) do
+ data
+ |> jpeg_find_exif_tiff()
+ |> exif_ascii_tag(@image_description_exif_tag)
+ rescue
+ _ -> nil
+ end
- tag_content = String.trim(tag_content)
+ defp jpeg_iptc_caption_abstract(data) do
+ data
+ |> jpeg_find_app13_photoshop()
+ |> photoshop_irb_caption_abstract()
+ rescue
+ _ -> nil
+ end
- if tag_content != "" and
- String.length(tag_content) <=
- Pleroma.Config.get([:instance, :description_limit]),
- do: tag_content,
- else: nil
- rescue
- _ in ErlangError -> nil
+ defp png_image_description(data) do
+ case png_text_keyword(data, "exif:ImageDescription") do
+ nil ->
+ data
+ |> png_find_exif_tiff()
+ |> exif_ascii_tag(@image_description_exif_tag)
+
+ description ->
+ description
+ end
+ rescue
+ _ -> nil
+ end
+
+ defp png_iptc_caption_abstract(data) do
+ iptc_profile_data =
+ data
+ |> png_text_or_compressed_text_keyword("Raw profile type iptc")
+ |> decode_imagemagick_profile()
+
+ caption =
+ case iptc_profile_data do
+ nil -> nil
+ iptc -> iptc_caption_abstract(iptc)
end
+
+ if is_binary(caption) do
+ caption
else
+ data
+ |> png_text_or_compressed_text_keyword("Raw profile type 8bim")
+ |> decode_imagemagick_profile()
+ |> photoshop_irb_caption_abstract()
+ end
+ rescue
+ _ -> nil
+ end
+
+ defp webp_image_description(data) do
+ data
+ |> webp_find_exif_tiff()
+ |> exif_ascii_tag(@image_description_exif_tag)
+ rescue
+ _ -> nil
+ end
+
+ defp gif_image_description(data) do
+ data
+ |> gif_find_xmp()
+ |> xmp_image_description()
+ rescue
+ _ -> nil
+ end
+
+ defp gif_find_xmp(data) when is_binary(data) do
+ case :binary.match(data, <<0x21, 0xFF, 0x0B, "XMP DataXMP">>) do
+ {start, _len} ->
+ xmp_start = start + 3 + byte_size("XMP DataXMP")
+ gif_read_sub_blocks(binary_part(data, xmp_start, byte_size(data) - xmp_start), [])
+
+ :nomatch ->
+ nil
+ end
+ end
+
+ defp gif_read_sub_blocks(<<0, _rest::binary>>, acc),
+ do: IO.iodata_to_binary(Enum.reverse(acc))
+
+ defp gif_read_sub_blocks(<<size::8, rest::binary>>, acc) when byte_size(rest) >= size do
+ <<chunk::binary-size(size), rest::binary>> = rest
+ gif_read_sub_blocks(rest, [chunk | acc])
+ end
+
+ defp gif_read_sub_blocks(_rest, _acc), do: nil
+
+ defp xmp_image_description(nil), do: nil
+
+ defp xmp_image_description(xmp) when is_binary(xmp) do
+ xmp = xmp |> to_utf8()
+
+ with [inner] <-
+ Regex.run(
+ ~r/<tiff:ImageDescription(?:\s[^>]*)?>(.*?)<\/tiff:ImageDescription>/s,
+ xmp,
+ capture: :all_but_first
+ ) do
+ case Regex.scan(~r/<rdf:li([^>]*)>(.*?)<\/rdf:li>/s, inner, capture: :all_but_first) do
+ [] ->
+ String.trim(inner)
+
+ lis ->
+ parsed =
+ Enum.map(lis, fn [attrs, value] ->
+ {attrs, String.trim(value)}
+ end)
+
+ Enum.find_value(parsed, fn {attrs, value} ->
+ if value != "" and String.contains?(attrs, "x-default"), do: value
+ end) ||
+ Enum.find_value(parsed, fn {_attrs, value} ->
+ if value != "", do: value
+ end)
+ end
+ else
+ _ -> nil
+ end
+ end
+
+ defp isobmff_image_description(data) do
+ data
+ |> isobmff_find_exif_tiff()
+ |> exif_ascii_tag(@image_description_exif_tag)
+ rescue
+ _ -> nil
+ end
+
+ defp jpeg_find_exif_tiff(<<@jpg_soi_marker, rest::binary>>),
+ do: jpeg_find_exif_tiff_segments(rest)
+
+ defp jpeg_find_exif_tiff(_), do: nil
+
+ defp jpeg_find_exif_tiff_segments(<<0xFF, 0xDA, _rest::binary>>), do: nil
+
+ defp jpeg_find_exif_tiff_segments(<<0xFF, 0xFF, rest::binary>>),
+ do: jpeg_find_exif_tiff_segments(<<0xFF, rest::binary>>)
+
+ defp jpeg_find_exif_tiff_segments(<<0xFF, marker, rest::binary>>) when marker in 0xD0..0xD9,
+ do: jpeg_find_exif_tiff_segments(rest)
+
+ defp jpeg_find_exif_tiff_segments(<<0xFF, marker, segment_length::16, rest::binary>>)
+ when segment_length >= 2 and byte_size(rest) >= segment_length - 2 do
+ segment_data_len = segment_length - 2
+ <<segment_data::binary-size(segment_data_len), remaining::binary>> = rest
+
+ if marker == 0xE1 and match?(<<"Exif", 0, 0, _::binary>>, segment_data) do
+ <<"Exif", 0, 0, tiff::binary>> = segment_data
+ tiff
+ else
+ jpeg_find_exif_tiff_segments(remaining)
+ end
+ end
+
+ defp jpeg_find_exif_tiff_segments(_), do: nil
+
+ defp jpeg_find_app13_photoshop(<<@jpg_soi_marker, rest::binary>>),
+ do: jpeg_find_photoshop_segments(rest)
+
+ defp jpeg_find_app13_photoshop(_), do: nil
+
+ defp jpeg_find_photoshop_segments(<<0xFF, 0xDA, _rest::binary>>), do: nil
+
+ defp jpeg_find_photoshop_segments(<<0xFF, 0xFF, rest::binary>>),
+ do: jpeg_find_photoshop_segments(<<0xFF, rest::binary>>)
+
+ defp jpeg_find_photoshop_segments(<<0xFF, marker, rest::binary>>) when marker in 0xD0..0xD9,
+ do: jpeg_find_photoshop_segments(rest)
+
+ defp jpeg_find_photoshop_segments(<<0xFF, marker, segment_length::16, rest::binary>>)
+ when segment_length >= 2 and byte_size(rest) >= segment_length - 2 do
+ segment_data_len = segment_length - 2
+ <<segment_data::binary-size(segment_data_len), remaining::binary>> = rest
+
+ if marker == 0xED and match?(<<"Photoshop 3.0", 0, _::binary>>, segment_data) do
+ segment_data
+ else
+ jpeg_find_photoshop_segments(remaining)
+ end
+ end
+
+ defp jpeg_find_photoshop_segments(_), do: nil
+
+ defp webp_find_exif_tiff(<<@webp_riff_marker, _riff_size::32-little, "WEBP", rest::binary>>),
+ do: webp_find_exif_chunk(rest)
+
+ defp webp_find_exif_tiff(_), do: nil
+
+ defp webp_find_exif_chunk(<<>>), do: nil
+
+ defp webp_find_exif_chunk(<<chunk_type::binary-size(4), chunk_size::32-little, rest::binary>>)
+ when byte_size(rest) >= chunk_size do
+ <<chunk_data::binary-size(chunk_size), after_chunk::binary>> = rest
+
+ remaining =
+ if rem(chunk_size, 2) == 1 and byte_size(after_chunk) >= 1 do
+ <<_pad::8, remaining::binary>> = after_chunk
+ remaining
+ else
+ after_chunk
+ end
+
+ if chunk_type == "EXIF" do
+ case chunk_data do
+ <<"Exif", 0, 0, tiff::binary>> -> tiff
+ tiff -> tiff
+ end
+ else
+ webp_find_exif_chunk(remaining)
+ end
+ end
+
+ defp webp_find_exif_chunk(_), do: nil
+
+ defp png_text_keyword(<<@png_signature, rest::binary>>, keyword),
+ do: png_find_text_chunk(rest, keyword)
+
+ defp png_text_keyword(_, _), do: nil
+
+ defp png_find_text_chunk(<<>>, _keyword), do: nil
+
+ defp png_find_text_chunk(
+ <<chunk_size::32, chunk_type::binary-size(4), chunk_data::binary-size(chunk_size),
+ _crc::32, rest::binary>>,
+ keyword
+ ) do
+ if chunk_type == "tEXt" do
+ case png_split_key_value(chunk_data) do
+ {^keyword, value} -> value
+ _ -> png_find_text_chunk(rest, keyword)
+ end
+ else
+ png_find_text_chunk(rest, keyword)
+ end
+ end
+
+ defp png_find_text_chunk(_rest, _keyword), do: nil
+
+ defp png_text_or_compressed_text_keyword(<<@png_signature, rest::binary>>, keyword),
+ do: png_find_text_or_compressed_chunk(rest, keyword)
+
+ defp png_text_or_compressed_text_keyword(_, _), do: nil
+
+ defp png_find_text_or_compressed_chunk(<<>>, _keyword), do: nil
+
+ defp png_find_text_or_compressed_chunk(
+ <<chunk_size::32, chunk_type::binary-size(4), chunk_data::binary-size(chunk_size),
+ _crc::32, rest::binary>>,
+ keyword
+ ) do
+ cond do
+ chunk_type == "tEXt" ->
+ case png_split_key_value(chunk_data) do
+ {^keyword, value} -> value
+ _ -> png_find_text_or_compressed_chunk(rest, keyword)
+ end
+
+ chunk_type == "zTXt" ->
+ case png_split_key_value(chunk_data) do
+ {^keyword, <<_compression_method::8, compressed::binary>>} ->
+ case safe_uncompress(compressed) do
+ {:ok, uncompressed} -> uncompressed
+ :error -> png_find_text_or_compressed_chunk(rest, keyword)
+ end
+
+ _ ->
+ png_find_text_or_compressed_chunk(rest, keyword)
+ end
+
+ true ->
+ png_find_text_or_compressed_chunk(rest, keyword)
+ end
+ end
+
+ defp png_find_text_or_compressed_chunk(_rest, _keyword), do: nil
+
+ defp png_find_exif_tiff(<<@png_signature, rest::binary>>), do: png_find_exif_chunk(rest)
+ defp png_find_exif_tiff(_), do: nil
+
+ defp png_find_exif_chunk(<<>>), do: nil
+
+ defp png_find_exif_chunk(
+ <<chunk_size::32, chunk_type::binary-size(4), chunk_data::binary-size(chunk_size),
+ _crc::32, rest::binary>>
+ ) do
+ cond do
+ chunk_type == "eXIf" -> chunk_data
+ chunk_type == "IEND" -> nil
+ true -> png_find_exif_chunk(rest)
+ end
+ end
+
+ defp png_find_exif_chunk(_), do: nil
+
+ defp png_split_key_value(chunk_data) when is_binary(chunk_data) do
+ case :binary.match(chunk_data, <<0>>) do
+ {idx, 1} ->
+ key = binary_part(chunk_data, 0, idx) |> to_utf8()
+ value = binary_part(chunk_data, idx + 1, byte_size(chunk_data) - idx - 1)
+ {key, value}
+
+ :nomatch ->
+ nil
+ end
+ end
+
+ defp safe_uncompress(data) when is_binary(data) do
+ try do
+ {:ok, :zlib.uncompress(data)}
+ rescue
+ _ -> :error
+ end
+ end
+
+ defp decode_imagemagick_profile(nil), do: nil
+
+ defp decode_imagemagick_profile(profile_text) when is_binary(profile_text) do
+ lines = profile_text |> to_utf8() |> String.split(~r/\r?\n/, trim: true)
+
+ case lines do
+ [_profile_name, _length | hex_lines] ->
+ hex = hex_lines |> Enum.join("") |> String.replace(~r/\s+/, "")
+
+ case Base.decode16(hex, case: :mixed) do
+ {:ok, data} -> data
+ :error -> nil
+ end
+
+ _ ->
+ nil
+ end
+ end
+
+ defp exif_ascii_tag(nil, _tag), do: nil
+
+ defp exif_ascii_tag(<<"II", 0x002A::16-little, ifd0_offset::32-little, _::binary>> = tiff, tag) do
+ exif_ifd0_ascii_tag(tiff, ifd0_offset, :little, tag)
+ end
+
+ defp exif_ascii_tag(<<"MM", 0x002A::16-big, ifd0_offset::32-big, _::binary>> = tiff, tag) do
+ exif_ifd0_ascii_tag(tiff, ifd0_offset, :big, tag)
+ end
+
+ defp exif_ascii_tag(_, _tag), do: nil
+
+ defp exif_ifd0_ascii_tag(tiff, ifd0_offset, endian, tag)
+ when byte_size(tiff) >= ifd0_offset + 2 do
+ <<_::binary-size(ifd0_offset), ifd0::binary>> = tiff
+ {entry_count, rest} = exif_decode_u16(ifd0, endian)
+ entries_len = entry_count * 12
+
+ if byte_size(rest) < entries_len do
nil
+ else
+ <<entries::binary-size(entries_len), _::binary>> = rest
+ exif_entries_ascii_tag(entries, endian, tiff, tag)
+ end
+ end
+
+ defp exif_ifd0_ascii_tag(_tiff, _ifd0_offset, _endian, _tag), do: nil
+
+ defp exif_entries_ascii_tag(<<>>, _endian, _tiff, _tag), do: nil
+
+ defp exif_entries_ascii_tag(<<entry::binary-size(12), rest::binary>>, endian, tiff, tag) do
+ {entry_tag, entry_type, entry_count, entry_value, entry_value_bytes} =
+ case endian do
+ :little ->
+ <<entry_tag::16-little, entry_type::16-little, entry_count::32-little,
+ entry_value_bytes::binary-size(4)>> = entry
+
+ entry_value = :binary.decode_unsigned(entry_value_bytes, :little)
+ {entry_tag, entry_type, entry_count, entry_value, entry_value_bytes}
+
+ :big ->
+ <<entry_tag::16-big, entry_type::16-big, entry_count::32-big,
+ entry_value_bytes::binary-size(4)>> = entry
+
+ entry_value = :binary.decode_unsigned(entry_value_bytes, :big)
+ {entry_tag, entry_type, entry_count, entry_value, entry_value_bytes}
+ end
+
+ if entry_tag == tag and entry_type == 2 do
+ exif_ascii_value(tiff, entry_count, entry_value, entry_value_bytes)
+ else
+ exif_entries_ascii_tag(rest, endian, tiff, tag)
+ end
+ end
+
+ defp exif_ascii_value(_tiff, 0, _offset, _inline), do: nil
+
+ defp exif_ascii_value(_tiff, count, _offset, inline) when count <= 4 do
+ inline |> binary_part(0, count) |> :binary.replace(<<0>>, <<>>, [:global])
+ end
+
+ defp exif_ascii_value(tiff, count, offset, _inline) do
+ if byte_size(tiff) >= offset + count do
+ tiff |> binary_part(offset, count) |> :binary.replace(<<0>>, <<>>, [:global])
+ else
+ nil
+ end
+ end
+
+ defp exif_decode_u16(<<value::16-little, rest::binary>>, :little), do: {value, rest}
+ defp exif_decode_u16(<<value::16-big, rest::binary>>, :big), do: {value, rest}
+
+ defp photoshop_irb_caption_abstract(nil), do: nil
+
+ defp photoshop_irb_caption_abstract(<<"Photoshop 3.0", 0, rest::binary>>),
+ do: photoshop_irb_caption_abstract(rest)
+
+ defp photoshop_irb_caption_abstract(data) when is_binary(data),
+ do: photoshop_irb_blocks_caption_abstract(data)
+
+ defp photoshop_irb_blocks_caption_abstract(<<>>), do: nil
+
+ defp photoshop_irb_blocks_caption_abstract(<<"8BIM", resource_id::16-big, rest::binary>>) do
+ with {:ok, _name, rest} <- photoshop_read_pascal_string(rest),
+ {:ok, data_size, rest} <- photoshop_read_u32(rest),
+ {:ok, resource_data, rest} <- photoshop_read_data_with_padding(rest, data_size) do
+ if resource_id == 0x0404 do
+ iptc_caption_abstract(resource_data)
+ else
+ photoshop_irb_blocks_caption_abstract(rest)
+ end
+ else
+ _ -> nil
+ end
+ end
+
+ defp photoshop_irb_blocks_caption_abstract(<<_::8, rest::binary>>),
+ do: photoshop_irb_blocks_caption_abstract(rest)
+
+ defp photoshop_read_pascal_string(<<len::8, rest::binary>>) when byte_size(rest) >= len do
+ <<name::binary-size(len), rest::binary>> = rest
+
+ rest =
+ if rem(len + 1, 2) == 1 and byte_size(rest) >= 1 do
+ <<_pad::8, remaining::binary>> = rest
+ remaining
+ else
+ rest
+ end
+
+ {:ok, name, rest}
+ end
+
+ defp photoshop_read_pascal_string(_), do: {:error, :invalid_pascal_string}
+
+ defp photoshop_read_u32(<<value::32-big, rest::binary>>), do: {:ok, value, rest}
+ defp photoshop_read_u32(_), do: {:error, :invalid_u32}
+
+ defp photoshop_read_data_with_padding(rest, size) when byte_size(rest) >= size do
+ <<data::binary-size(size), remaining::binary>> = rest
+
+ remaining =
+ if rem(size, 2) == 1 and byte_size(remaining) >= 1 do
+ <<_pad::8, after_pad::binary>> = remaining
+ after_pad
+ else
+ remaining
+ end
+
+ {:ok, data, remaining}
+ end
+
+ defp photoshop_read_data_with_padding(_rest, _size), do: {:error, :invalid_data}
+
+ defp iptc_caption_abstract(nil), do: nil
+
+ defp iptc_caption_abstract(data) when is_binary(data) do
+ case iptc_find_dataset(data) do
+ nil ->
+ case photoshop_irb_caption_abstract(data) do
+ nil -> nil
+ caption -> caption
+ end
+
+ caption ->
+ caption
+ end
+ end
+
+ defp iptc_find_dataset(<<@iptc_marker, record::8, dataset::8, length::16-big, rest::binary>>)
+ when byte_size(rest) >= length do
+ <<value::binary-size(length), remaining::binary>> = rest
+
+ if record == @iptc_caption_record and dataset == @iptc_caption_dataset do
+ value
+ else
+ iptc_find_dataset(remaining)
+ end
+ end
+
+ defp iptc_find_dataset(<<_::8, rest::binary>>), do: iptc_find_dataset(rest)
+ defp iptc_find_dataset(<<>>), do: nil
+
+ # ISO Base Media File Format (e.g. HEIC, AVIF)
+ defp isobmff_find_exif_tiff(data) do
+ with {:ok, meta_payload} <- isobmff_find_meta_payload(data),
+ {:ok, iinf_box} <- isobmff_find_child_box(meta_payload, "iinf"),
+ {:ok, iloc_box} <- isobmff_find_child_box(meta_payload, "iloc"),
+ {:ok, exif_item_ids} <- isobmff_exif_item_ids(iinf_box),
+ {:ok, item_locations} <- isobmff_item_locations(iloc_box),
+ [item_id | _] <- exif_item_ids,
+ extents when is_list(extents) and extents != [] <- Map.get(item_locations, item_id),
+ exif_item <- isobmff_read_item(data, extents),
+ tiff <- isobmff_exif_item_tiff(exif_item) do
+ tiff
+ else
+ _ -> nil
+ end
+ end
+
+ defp isobmff_find_meta_payload(data) do
+ case isobmff_find_top_box(data, "meta") do
+ {:ok, meta_box} ->
+ with {:ok, payload} <- isobmff_box_payload(meta_box),
+ <<_version::8, _flags::24, rest::binary>> <- payload do
+ {:ok, rest}
+ else
+ _ -> {:error, :invalid_meta_box}
+ end
+
+ _ ->
+ {:error, :no_meta_box}
end
end
+
+ defp isobmff_find_top_box(data, wanted_type) when is_binary(data),
+ do: do_isobmff_find_box(data, wanted_type)
+
+ defp do_isobmff_find_box(<<>>, _wanted_type), do: {:error, :not_found}
+
+ defp do_isobmff_find_box(data, wanted_type) do
+ with {:ok, box_type, box_size, _header_size} <- isobmff_box_header(data),
+ true <- byte_size(data) >= box_size do
+ <<box::binary-size(box_size), rest::binary>> = data
+
+ if box_type == wanted_type do
+ {:ok, box}
+ else
+ do_isobmff_find_box(rest, wanted_type)
+ end
+ else
+ _ -> {:error, :invalid_isobmff}
+ end
+ end
+
+ defp isobmff_find_child_box(data, wanted_type) when is_binary(data),
+ do: do_isobmff_find_box(data, wanted_type)
+
+ defp isobmff_box_header(<<size::32-big, type::binary-size(4), _rest::binary>>) when size >= 8,
+ do: {:ok, type, size, 8}
+
+ defp isobmff_box_header(<<1::32-big, type::binary-size(4), size::64-big, _rest::binary>>)
+ when size >= 16,
+ do: {:ok, type, size, 16}
+
+ defp isobmff_box_header(_), do: {:error, :invalid_box_header}
+
+ defp isobmff_box_payload(box) when is_binary(box) do
+ with {:ok, _type, box_size, header_size} <- isobmff_box_header(box),
+ true <- byte_size(box) >= box_size,
+ payload_size <- box_size - header_size,
+ payload when payload_size >= 0 <- binary_part(box, header_size, payload_size) do
+ {:ok, payload}
+ else
+ _ -> {:error, :invalid_box}
+ end
+ end
+
+ defp isobmff_exif_item_ids(iinf_box) do
+ with {:ok, payload} <- isobmff_box_payload(iinf_box),
+ <<version::8, _flags::24, rest::binary>> <- payload do
+ {item_count, rest} =
+ case version do
+ 0 -> decode_u16_be(rest)
+ 1 -> decode_u16_be(rest)
+ _ -> decode_u32_be(rest)
+ end
+
+ {ids, _rest} = isobmff_collect_infe_exif_ids(rest, item_count, [])
+ {:ok, Enum.reverse(ids)}
+ else
+ _ -> {:error, :invalid_iinf_box}
+ end
+ end
+
+ defp isobmff_collect_infe_exif_ids(rest, 0, acc), do: {acc, rest}
+
+ defp isobmff_collect_infe_exif_ids(rest, remaining, acc) do
+ with {:ok, box_type, box_size, _header_size} <- isobmff_box_header(rest),
+ true <- byte_size(rest) >= box_size,
+ <<box::binary-size(box_size), next::binary>> <- rest do
+ acc =
+ if box_type == "infe" do
+ case isobmff_infe_item(box) do
+ {:ok, item_id, "Exif"} -> [item_id | acc]
+ _ -> acc
+ end
+ else
+ acc
+ end
+
+ isobmff_collect_infe_exif_ids(next, remaining - 1, acc)
+ else
+ _ -> {acc, rest}
+ end
+ end
+
+ defp isobmff_infe_item(infe_box) do
+ with {:ok, payload} <- isobmff_box_payload(infe_box),
+ <<version::8, _flags::24, rest::binary>> <- payload do
+ case version do
+ 2 ->
+ with {item_id, rest} <- decode_u16_be(rest),
+ {_protection, rest} <- decode_u16_be(rest),
+ <<item_type::binary-size(4), _::binary>> <- rest do
+ {:ok, item_id, item_type}
+ else
+ _ -> {:error, :invalid_infe}
+ end
+
+ 3 ->
+ with {item_id, rest} <- decode_u32_be(rest),
+ {_protection, rest} <- decode_u16_be(rest),
+ <<item_type::binary-size(4), _::binary>> <- rest do
+ {:ok, item_id, item_type}
+ else
+ _ -> {:error, :invalid_infe}
+ end
+
+ _ ->
+ {:error, :unsupported_infe}
+ end
+ else
+ _ -> {:error, :invalid_infe}
+ end
+ end
+
+ defp isobmff_item_locations(iloc_box) do
+ with {:ok, payload} <- isobmff_box_payload(iloc_box),
+ <<version::8, _flags::24, rest::binary>> <- payload,
+ <<offset_and_length_sizes::8, base_and_reserved::8, rest::binary>> <- rest do
+ offset_size = div(offset_and_length_sizes, 16)
+ length_size = rem(offset_and_length_sizes, 16)
+ base_offset_size = div(base_and_reserved, 16)
+
+ {index_size, rest} =
+ if version in [1, 2] do
+ <<index_and_reserved::8, rest::binary>> = rest
+ {div(index_and_reserved, 16), rest}
+ else
+ {0, rest}
+ end
+
+ {item_count, rest} =
+ if version == 2 do
+ decode_u32_be(rest)
+ else
+ decode_u16_be(rest)
+ end
+
+ {locations, _rest} =
+ isobmff_parse_iloc_items(
+ rest,
+ version,
+ item_count,
+ offset_size,
+ length_size,
+ base_offset_size,
+ index_size,
+ %{}
+ )
+
+ {:ok, locations}
+ else
+ _ -> {:error, :invalid_iloc}
+ end
+ end
+
+ defp isobmff_parse_iloc_items(rest, _version, 0, _os, _ls, _bos, _is, acc), do: {acc, rest}
+
+ defp isobmff_parse_iloc_items(
+ rest,
+ version,
+ remaining,
+ offset_size,
+ length_size,
+ base_offset_size,
+ index_size,
+ acc
+ ) do
+ {item_id, rest} =
+ if version == 2 do
+ decode_u32_be(rest)
+ else
+ decode_u16_be(rest)
+ end
+
+ rest =
+ if version in [1, 2] do
+ <<_reserved::12, _construction_method::4, rest::binary>> = rest
+ rest
+ else
+ rest
+ end
+
+ {_data_reference_index, rest} = decode_u16_be(rest)
+
+ {base_offset, rest} = decode_uint_be(rest, base_offset_size)
+ {extent_count, rest} = decode_u16_be(rest)
+
+ {extents, rest} =
+ isobmff_parse_iloc_extents(
+ rest,
+ version,
+ extent_count,
+ offset_size,
+ length_size,
+ index_size,
+ base_offset,
+ []
+ )
+
+ acc = Map.put(acc, item_id, extents)
+
+ isobmff_parse_iloc_items(
+ rest,
+ version,
+ remaining - 1,
+ offset_size,
+ length_size,
+ base_offset_size,
+ index_size,
+ acc
+ )
+ rescue
+ _ -> {acc, rest}
+ end
+
+ defp isobmff_parse_iloc_extents(rest, _version, 0, _os, _ls, _is, _base_offset, acc),
+ do: {Enum.reverse(acc), rest}
+
+ defp isobmff_parse_iloc_extents(
+ rest,
+ version,
+ remaining,
+ offset_size,
+ length_size,
+ index_size,
+ base_offset,
+ acc
+ ) do
+ rest =
+ if version in [1, 2] and index_size > 0 do
+ <<_extent_index::binary-size(index_size), rest::binary>> = rest
+ rest
+ else
+ rest
+ end
+
+ {extent_offset, rest} = decode_uint_be(rest, offset_size)
+ {extent_length, rest} = decode_uint_be(rest, length_size)
+
+ abs_offset = base_offset + extent_offset
+ extent = %{offset: abs_offset, length: extent_length}
+
+ isobmff_parse_iloc_extents(
+ rest,
+ version,
+ remaining - 1,
+ offset_size,
+ length_size,
+ index_size,
+ base_offset,
+ [extent | acc]
+ )
+ end
+
+ defp isobmff_read_item(data, extents) do
+ extents = Enum.sort_by(extents, & &1.offset)
+
+ extents
+ |> Enum.map(fn %{offset: offset, length: length} -> binary_part(data, offset, length) end)
+ |> IO.iodata_to_binary()
+ end
+
+ defp isobmff_exif_item_tiff(<<tiff_offset::32-big, rest::binary>>)
+ when byte_size(rest) >= tiff_offset do
+ <<_prelude::binary-size(tiff_offset), tiff::binary>> = rest
+ tiff
+ end
+
+ defp isobmff_exif_item_tiff(_), do: nil
+
+ defp decode_u16_be(<<value::16-big, rest::binary>>), do: {value, rest}
+ defp decode_u32_be(<<value::32-big, rest::binary>>), do: {value, rest}
+
+ defp decode_uint_be(rest, 0), do: {0, rest}
+
+ defp decode_uint_be(rest, bytes) when bytes in 1..8 and byte_size(rest) >= bytes do
+ <<value::binary-size(bytes), rest::binary>> = rest
+ {:binary.decode_unsigned(value, :big), rest}
+ end
end
diff --git a/lib/pleroma/upload/filter/exiftool/strip_location.ex b/lib/pleroma/upload/filter/exiftool/strip_location.ex
index 512d8df9d..8b69388ce 100644
--- a/lib/pleroma/upload/filter/exiftool/strip_location.ex
+++ b/lib/pleroma/upload/filter/exiftool/strip_location.ex
@@ -1,233 +1,667 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Upload.Filter.Exiftool.StripLocation do
@moduledoc """
Strips GPS related EXIF tags and overwrites the file in place.
Also strips or replaces filesystem metadata e.g., timestamps.
"""
@behaviour Pleroma.Upload.Filter
- # Formats not compatible with exiftool at this time
- def filter(%Pleroma.Upload{content_type: "image/heic"}), do: {:ok, :noop}
- def filter(%Pleroma.Upload{content_type: "image/webp"}), do: {:ok, :noop}
- def filter(%Pleroma.Upload{content_type: "image/svg" <> _}), do: {:ok, :noop}
-
- def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
- if Pleroma.Utils.command_available?("exiftool") do
- strip_with_exiftool(file)
- else
- strip_with_elixir(file)
- end
- end
+ def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}),
+ do: strip_with_elixir(file)
def filter(_), do: {:ok, :noop}
- defp strip_with_exiftool(file) do
- try do
- case System.cmd("exiftool", ["-m", "-overwrite_original", "-gps:all=", "-png:all=", file],
- stderr_to_stdout: true,
- parallelism: true
- ) do
- {_response, 0} -> {:ok, :filtered}
- {error, _} -> {:error, error}
- end
- rescue
- e in ErlangError ->
- {:error, "#{__MODULE__}: #{inspect(e)}"}
- end
- end
-
defp strip_with_elixir(file) do
case ExifGpsStripper.strip_gps_data(file, file) do
:ok -> {:ok, :filtered}
:noop -> {:ok, :noop}
{:error, reason} -> {:error, reason}
end
end
end
defmodule ExifGpsStripper do
@moduledoc false
@gps_ifd_tag 0x8825
@png_signature <<0x89, "PNG", 0x0D, 0x0A, 0x1A, 0x0A>>
@jpg_soi_marker <<0xFF, 0xD8>>
+ @webp_riff_marker "RIFF"
@jpg_app1_marker 0xE1
@jpg_sos_marker 0xDA
@jpg_tem_marker 0x01
@type result :: :ok | :noop | {:error, term()}
@spec strip_gps_data(Path.t(), Path.t()) :: result()
def strip_gps_data(input_path, output_path) do
with {:ok, data} <- File.read(input_path),
{:ok, stripped_data} <- process_file(data),
:ok <- File.write(output_path, stripped_data) do
:ok
else
:noop -> :noop
{:error, _} = error -> error
end
rescue
e ->
{:error, "#{__MODULE__}: #{inspect(e)}"}
end
defp process_file(<<@png_signature, rest::binary>>) do
case strip_png_chunks(rest, [], false) do
{:ok, new_rest, true} -> {:ok, @png_signature <> new_rest}
{:ok, _new_rest, false} -> :noop
{:error, _} = error -> error
end
end
defp process_file(<<@jpg_soi_marker, rest::binary>>) do
case strip_jpeg_segments(rest, [], false) do
{:ok, iodata, true} -> {:ok, @jpg_soi_marker <> IO.iodata_to_binary(iodata)}
{:ok, _iodata, false} -> :noop
{:error, _} = error -> error
end
end
+ defp process_file(<<@webp_riff_marker, riff_size::32-little, "WEBP", rest::binary>>) do
+ case strip_webp_chunks(rest, [], false) do
+ {:ok, iodata, true} ->
+ {:ok,
+ @webp_riff_marker <> <<riff_size::32-little>> <> "WEBP" <> IO.iodata_to_binary(iodata)}
+
+ {:ok, _iodata, false} ->
+ :noop
+
+ {:error, _} = error ->
+ error
+ end
+ end
+
+ defp process_file(<<_size::32-big, "ftyp", _rest::binary>> = data) do
+ case strip_isobmff_exif_items(data) do
+ {:ok, new_data, true} -> {:ok, new_data}
+ {:ok, _new_data, false} -> :noop
+ {:error, _} = error -> error
+ end
+ end
+
defp process_file(_), do: :noop
defp strip_png_chunks(<<>>, acc, stripped?),
do: {:ok, IO.iodata_to_binary(Enum.reverse(acc)), stripped?}
defp strip_png_chunks(
<<chunk_size::32, chunk_type::binary-size(4), chunk_data::binary-size(chunk_size),
crc::32, rest::binary>>,
acc,
stripped?
) do
if chunk_type in ["eXIf", "tEXt", "zTXt", "iTXt"] do
strip_png_chunks(rest, acc, true)
else
chunk =
<<chunk_size::32, chunk_type::binary-size(4), chunk_data::binary-size(chunk_size),
crc::32>>
strip_png_chunks(rest, [chunk | acc], stripped?)
end
end
defp strip_png_chunks(_data, _acc, _stripped?), do: {:error, :invalid_png}
+ defp strip_webp_chunks(<<>>, acc, stripped?),
+ do: {:ok, Enum.reverse(acc), stripped?}
+
+ defp strip_webp_chunks(
+ <<chunk_type::binary-size(4), chunk_size::32-little, rest::binary>>,
+ acc,
+ stripped?
+ )
+ when byte_size(rest) >= chunk_size do
+ <<chunk_data::binary-size(chunk_size), after_chunk::binary>> = rest
+
+ {padding, remaining} =
+ if rem(chunk_size, 2) == 1 and byte_size(after_chunk) >= 1 do
+ <<pad::binary-size(1), remaining::binary>> = after_chunk
+ {pad, remaining}
+ else
+ {<<>>, after_chunk}
+ end
+
+ {chunk_data, patched?} =
+ if chunk_type == "EXIF" do
+ strip_gps_from_exif_payload(chunk_data)
+ else
+ {chunk_data, false}
+ end
+
+ chunk = <<chunk_type::binary-size(4), chunk_size::32-little>> <> chunk_data <> padding
+ strip_webp_chunks(remaining, [chunk | acc], stripped? or patched?)
+ end
+
+ defp strip_webp_chunks(_data, _acc, _stripped?), do: {:error, :invalid_webp}
+
defp strip_jpeg_segments(<<0xFF, @jpg_sos_marker, _::binary>> = data, acc, changed?) do
{:ok, Enum.reverse([data | acc]), changed?}
end
defp strip_jpeg_segments(<<0xFF, 0xFF, rest::binary>>, acc, changed?) do
strip_jpeg_segments(<<0xFF, rest::binary>>, acc, changed?)
end
defp strip_jpeg_segments(<<0xFF, @jpg_tem_marker, rest::binary>>, acc, changed?) do
strip_jpeg_segments(rest, [<<0xFF, @jpg_tem_marker>> | acc], changed?)
end
defp strip_jpeg_segments(<<0xFF, marker, rest::binary>>, acc, changed?)
when marker in 0xD0..0xD9 do
strip_jpeg_segments(rest, [<<0xFF, marker>> | acc], changed?)
end
defp strip_jpeg_segments(<<0xFF, marker, segment_length::16, rest::binary>>, acc, changed?) do
segment_data_len = segment_length - 2
if segment_length >= 2 and byte_size(rest) >= segment_data_len do
<<segment_data::binary-size(segment_data_len), remaining::binary>> = rest
{segment_data, patched?} =
if marker == @jpg_app1_marker do
strip_gps_from_app1_segment(segment_data)
else
{segment_data, false}
end
segment = <<0xFF, marker, segment_length::16>> <> segment_data
strip_jpeg_segments(remaining, [segment | acc], changed? or patched?)
else
{:error, :invalid_jpeg}
end
end
defp strip_jpeg_segments(<<>>, acc, changed?), do: {:ok, Enum.reverse(acc), changed?}
defp strip_jpeg_segments(_data, _acc, _changed?), do: {:error, :invalid_jpeg}
defp strip_gps_from_app1_segment(<<"Exif", 0, 0, tiff::binary>> = segment_data) do
case strip_gps_from_tiff(tiff) do
{:ok, _tiff, false} -> {segment_data, false}
{:ok, new_tiff, true} -> {<<"Exif", 0, 0>> <> new_tiff, true}
{:error, _reason} -> {segment_data, false}
end
end
defp strip_gps_from_app1_segment(segment_data), do: {segment_data, false}
+ defp strip_gps_from_exif_payload(payload) when is_binary(payload) do
+ case strip_gps_from_tiff(payload) do
+ {:ok, new_tiff, patched?} -> {new_tiff, patched?}
+ {:error, _reason} -> {payload, false}
+ end
+ end
+
defp strip_gps_from_tiff(<<"II", 0x002A::16-little, ifd0_offset::32-little, _::binary>> = tiff) do
strip_gps_from_ifd0(tiff, ifd0_offset, :little)
end
defp strip_gps_from_tiff(<<"MM", 0x002A::16-big, ifd0_offset::32-big, _::binary>> = tiff) do
strip_gps_from_ifd0(tiff, ifd0_offset, :big)
end
defp strip_gps_from_tiff(_), do: {:error, :unsupported_tiff}
defp strip_gps_from_ifd0(tiff, ifd0_offset, endian) do
if byte_size(tiff) < ifd0_offset + 2 do
{:error, :invalid_ifd0_offset}
else
<<before_ifd0::binary-size(ifd0_offset), ifd0::binary>> = tiff
{entry_count, entry_count_bytes, ifd0_entries_and_rest} =
case endian do
:little ->
<<count::16-little, rest::binary>> = ifd0
{count, <<count::16-little>>, rest}
:big ->
<<count::16-big, rest::binary>> = ifd0
{count, <<count::16-big>>, rest}
end
entries_len = entry_count * 12
if byte_size(ifd0_entries_and_rest) < entries_len do
{:error, :invalid_ifd0}
else
<<entries::binary-size(entries_len), rest_after_entries::binary>> = ifd0_entries_and_rest
{patched_entries, patched?} = patch_ifd_entries(entries, endian, false, [])
new_tiff = before_ifd0 <> entry_count_bytes <> patched_entries <> rest_after_entries
{:ok, new_tiff, patched?}
end
end
end
defp patch_ifd_entries(<<>>, _endian, patched?, acc),
do: {IO.iodata_to_binary(Enum.reverse(acc)), patched?}
defp patch_ifd_entries(<<entry::binary-size(12), rest::binary>>, endian, patched?, acc) do
tag =
case endian do
:little ->
<<tag::16-little, _::binary-size(10)>> = entry
tag
:big ->
<<tag::16-big, _::binary-size(10)>> = entry
tag
end
if tag == @gps_ifd_tag do
patch_ifd_entries(rest, endian, true, [<<0::size(12)-unit(8)>> | acc])
else
patch_ifd_entries(rest, endian, patched?, [entry | acc])
end
end
+
+ defp strip_isobmff_exif_items(data) when is_binary(data) do
+ with {:ok, meta_payload} <- isobmff_find_meta_payload(data),
+ {:ok, iinf_box} <- isobmff_find_child_box(meta_payload, "iinf"),
+ {:ok, iloc_box} <- isobmff_find_child_box(meta_payload, "iloc"),
+ {:ok, exif_item_ids} <- isobmff_exif_item_ids(iinf_box),
+ {:ok, item_locations} <- isobmff_item_locations(iloc_box),
+ exif_locations <- isobmff_pick_items(item_locations, exif_item_ids),
+ {:ok, patches, changed?} <- isobmff_gps_patches(data, exif_locations) do
+ {:ok, apply_binary_patches(data, patches), changed?}
+ else
+ {:error, _} = error -> error
+ _ -> {:ok, data, false}
+ end
+ end
+
+ defp isobmff_find_meta_payload(data) do
+ case isobmff_find_top_box(data, "meta") do
+ {:ok, meta_box} ->
+ with {:ok, payload} <- isobmff_box_payload(meta_box),
+ <<_version::8, _flags::24, rest::binary>> <- payload do
+ {:ok, rest}
+ else
+ _ -> {:error, :invalid_meta_box}
+ end
+
+ _ ->
+ {:error, :no_meta_box}
+ end
+ end
+
+ defp isobmff_find_top_box(data, wanted_type) do
+ do_isobmff_find_top_box(data, wanted_type)
+ end
+
+ defp do_isobmff_find_top_box(<<>>, _wanted_type), do: {:error, :not_found}
+
+ defp do_isobmff_find_top_box(data, wanted_type) when is_binary(data) do
+ with {:ok, box_type, box_size, _header_size} <- isobmff_box_header(data),
+ true <- byte_size(data) >= box_size do
+ <<box::binary-size(box_size), rest::binary>> = data
+
+ if box_type == wanted_type do
+ {:ok, box}
+ else
+ do_isobmff_find_top_box(rest, wanted_type)
+ end
+ else
+ _ -> {:error, :invalid_isobmff}
+ end
+ end
+
+ defp isobmff_find_child_box(data, wanted_type) when is_binary(data) do
+ case do_isobmff_find_child_box(data, wanted_type) do
+ {:ok, box} -> {:ok, box}
+ _ -> {:error, :not_found}
+ end
+ end
+
+ defp do_isobmff_find_child_box(<<>>, _wanted_type), do: {:error, :not_found}
+
+ defp do_isobmff_find_child_box(data, wanted_type) do
+ with {:ok, box_type, box_size, _header_size} <- isobmff_box_header(data),
+ true <- byte_size(data) >= box_size do
+ <<box::binary-size(box_size), rest::binary>> = data
+
+ if box_type == wanted_type do
+ {:ok, box}
+ else
+ do_isobmff_find_child_box(rest, wanted_type)
+ end
+ else
+ _ -> {:error, :invalid_isobmff}
+ end
+ end
+
+ defp isobmff_box_header(<<size::32-big, type::binary-size(4), _rest::binary>>) when size >= 8,
+ do: {:ok, type, size, 8}
+
+ defp isobmff_box_header(<<1::32-big, type::binary-size(4), size::64-big, _rest::binary>>)
+ when size >= 16,
+ do: {:ok, type, size, 16}
+
+ defp isobmff_box_header(_), do: {:error, :invalid_box_header}
+
+ defp isobmff_box_payload(box) when is_binary(box) do
+ with {:ok, _type, box_size, header_size} <- isobmff_box_header(box),
+ true <- byte_size(box) >= box_size,
+ payload_size <- box_size - header_size,
+ payload when payload_size >= 0 <- binary_part(box, header_size, payload_size) do
+ {:ok, payload}
+ else
+ _ -> {:error, :invalid_box}
+ end
+ end
+
+ defp isobmff_exif_item_ids(iinf_box) do
+ with {:ok, payload} <- isobmff_box_payload(iinf_box),
+ <<version::8, _flags::24, rest::binary>> <- payload do
+ {item_count, rest} =
+ case version do
+ 0 -> decode_u16_be(rest)
+ 1 -> decode_u16_be(rest)
+ _ -> decode_u32_be(rest)
+ end
+
+ {ids, _rest} = isobmff_collect_infe_exif_ids(rest, item_count, [])
+ {:ok, Enum.reverse(ids)}
+ else
+ _ -> {:error, :invalid_iinf_box}
+ end
+ end
+
+ defp isobmff_collect_infe_exif_ids(rest, 0, acc), do: {acc, rest}
+
+ defp isobmff_collect_infe_exif_ids(rest, remaining, acc) do
+ with {:ok, box_type, box_size, _header_size} <- isobmff_box_header(rest),
+ true <- byte_size(rest) >= box_size,
+ <<box::binary-size(box_size), next::binary>> <- rest do
+ acc =
+ if box_type == "infe" do
+ case isobmff_infe_item(box) do
+ {:ok, item_id, "Exif"} -> [item_id | acc]
+ _ -> acc
+ end
+ else
+ acc
+ end
+
+ isobmff_collect_infe_exif_ids(next, remaining - 1, acc)
+ else
+ _ -> {acc, rest}
+ end
+ end
+
+ defp isobmff_infe_item(infe_box) do
+ with {:ok, payload} <- isobmff_box_payload(infe_box),
+ <<version::8, _flags::24, rest::binary>> <- payload do
+ case version do
+ 2 ->
+ with {item_id, rest} <- decode_u16_be(rest),
+ {_protection, rest} <- decode_u16_be(rest),
+ <<item_type::binary-size(4), _::binary>> <- rest do
+ {:ok, item_id, item_type}
+ else
+ _ -> {:error, :invalid_infe}
+ end
+
+ 3 ->
+ with {item_id, rest} <- decode_u32_be(rest),
+ {_protection, rest} <- decode_u16_be(rest),
+ <<item_type::binary-size(4), _::binary>> <- rest do
+ {:ok, item_id, item_type}
+ else
+ _ -> {:error, :invalid_infe}
+ end
+
+ _ ->
+ {:error, :unsupported_infe}
+ end
+ else
+ _ -> {:error, :invalid_infe}
+ end
+ end
+
+ defp isobmff_item_locations(iloc_box) do
+ with {:ok, payload} <- isobmff_box_payload(iloc_box),
+ <<version::8, _flags::24, rest::binary>> <- payload,
+ <<offset_and_length_sizes::8, base_and_reserved::8, rest::binary>> <- rest do
+ offset_size = div(offset_and_length_sizes, 16)
+ length_size = rem(offset_and_length_sizes, 16)
+ base_offset_size = div(base_and_reserved, 16)
+
+ {index_size, rest} =
+ if version in [1, 2] do
+ <<index_and_reserved::8, rest::binary>> = rest
+ {div(index_and_reserved, 16), rest}
+ else
+ {0, rest}
+ end
+
+ {item_count, rest} =
+ if version == 2 do
+ decode_u32_be(rest)
+ else
+ decode_u16_be(rest)
+ end
+
+ {locations, _rest} =
+ isobmff_parse_iloc_items(
+ rest,
+ version,
+ item_count,
+ offset_size,
+ length_size,
+ base_offset_size,
+ index_size,
+ %{}
+ )
+
+ {:ok, locations}
+ else
+ _ -> {:error, :invalid_iloc}
+ end
+ end
+
+ defp isobmff_parse_iloc_items(rest, _version, 0, _os, _ls, _bos, _is, acc), do: {acc, rest}
+
+ defp isobmff_parse_iloc_items(
+ rest,
+ version,
+ remaining,
+ offset_size,
+ length_size,
+ base_offset_size,
+ index_size,
+ acc
+ ) do
+ {item_id, rest} =
+ if version == 2 do
+ decode_u32_be(rest)
+ else
+ decode_u16_be(rest)
+ end
+
+ rest =
+ if version in [1, 2] do
+ <<_reserved::12, _construction_method::4, rest::binary>> = rest
+ rest
+ else
+ rest
+ end
+
+ {_data_reference_index, rest} = decode_u16_be(rest)
+
+ {base_offset, rest} = decode_uint_be(rest, base_offset_size)
+ {extent_count, rest} = decode_u16_be(rest)
+
+ {extents, rest} =
+ isobmff_parse_iloc_extents(
+ rest,
+ version,
+ extent_count,
+ offset_size,
+ length_size,
+ index_size,
+ base_offset,
+ []
+ )
+
+ acc = Map.update(acc, item_id, extents, fn existing -> existing ++ extents end)
+
+ isobmff_parse_iloc_items(
+ rest,
+ version,
+ remaining - 1,
+ offset_size,
+ length_size,
+ base_offset_size,
+ index_size,
+ acc
+ )
+ rescue
+ _ -> {acc, rest}
+ end
+
+ defp isobmff_parse_iloc_extents(rest, _version, 0, _os, _ls, _is, _base_offset, acc),
+ do: {Enum.reverse(acc), rest}
+
+ defp isobmff_parse_iloc_extents(
+ rest,
+ version,
+ remaining,
+ offset_size,
+ length_size,
+ index_size,
+ base_offset,
+ acc
+ ) do
+ rest =
+ if version in [1, 2] and index_size > 0 do
+ <<_extent_index::binary-size(index_size), rest::binary>> = rest
+ rest
+ else
+ rest
+ end
+
+ {extent_offset, rest} = decode_uint_be(rest, offset_size)
+ {extent_length, rest} = decode_uint_be(rest, length_size)
+
+ abs_offset = base_offset + extent_offset
+ extent = %{offset: abs_offset, length: extent_length}
+
+ isobmff_parse_iloc_extents(
+ rest,
+ version,
+ remaining - 1,
+ offset_size,
+ length_size,
+ index_size,
+ base_offset,
+ [extent | acc]
+ )
+ end
+
+ defp isobmff_pick_items(item_locations, ids) when is_map(item_locations) and is_list(ids) do
+ ids
+ |> Enum.map(fn id -> {id, Map.get(item_locations, id, [])} end)
+ |> Enum.reject(fn {_id, extents} -> extents == [] end)
+ end
+
+ defp isobmff_gps_patches(_data, []), do: {:ok, [], false}
+
+ defp isobmff_gps_patches(data, exif_items) do
+ {patches, changed?} =
+ Enum.reduce(exif_items, {[], false}, fn {_item_id, extents}, {acc, changed?} ->
+ extents = Enum.sort_by(extents, & &1.offset)
+
+ with true <- Enum.all?(extents, &is_map/1),
+ true <-
+ Enum.all?(extents, fn %{offset: offset, length: length} ->
+ length > 0 and byte_size(data) >= offset + length
+ end),
+ exif_item <-
+ Enum.map(extents, fn %{offset: offset, length: length} ->
+ binary_part(data, offset, length)
+ end),
+ exif_item <- IO.iodata_to_binary(exif_item),
+ {:ok, new_exif_item, true} <- patch_heif_exif_item(exif_item),
+ {:ok, item_patches} <- split_isobmff_item_to_patches(extents, new_exif_item) do
+ {acc ++ item_patches, true}
+ else
+ _ -> {acc, changed?}
+ end
+ end)
+
+ {:ok, patches, changed?}
+ end
+
+ defp split_isobmff_item_to_patches(extents, new_item) do
+ {patches, rest} =
+ Enum.reduce(extents, {[], new_item}, fn %{offset: offset, length: length},
+ {acc, remaining} ->
+ <<part::binary-size(length), rest::binary>> = remaining
+ {[{offset, length, part} | acc], rest}
+ end)
+
+ if rest == <<>> do
+ {:ok, Enum.reverse(patches)}
+ else
+ {:error, :length_mismatch}
+ end
+ rescue
+ _ -> {:error, :invalid_extents}
+ end
+
+ defp patch_heif_exif_item(<<tiff_offset::32-big, rest::binary>>)
+ when byte_size(rest) >= tiff_offset do
+ <<prelude::binary-size(tiff_offset), tiff::binary>> = rest
+
+ case strip_gps_from_tiff(tiff) do
+ {:ok, new_tiff, true} ->
+ {:ok, <<tiff_offset::32-big>> <> prelude <> new_tiff, true}
+
+ {:ok, _tiff, false} ->
+ {:ok, <<tiff_offset::32-big>> <> prelude <> tiff, false}
+
+ {:error, _} ->
+ {:error, :invalid_tiff}
+ end
+ end
+
+ defp patch_heif_exif_item(_), do: {:error, :invalid_exif_item}
+
+ defp apply_binary_patches(data, patches) do
+ patches =
+ patches
+ |> Enum.sort_by(fn {offset, _len, _replacement} -> offset end)
+
+ {iodata, pos} =
+ Enum.reduce(patches, {[], 0}, fn {offset, len, replacement}, {acc, pos} ->
+ acc = [acc, binary_part(data, pos, offset - pos), replacement]
+ {acc, offset + len}
+ end)
+
+ IO.iodata_to_binary([iodata, binary_part(data, pos, byte_size(data) - pos)])
+ end
+
+ defp decode_u16_be(<<value::16-big, rest::binary>>), do: {value, rest}
+ defp decode_u32_be(<<value::32-big, rest::binary>>), do: {value, rest}
+
+ defp decode_uint_be(rest, 0), do: {0, rest}
+
+ defp decode_uint_be(rest, bytes) when bytes in 1..8 and byte_size(rest) >= bytes do
+ <<value::binary-size(bytes), rest::binary>> = rest
+ {:binary.decode_unsigned(value, :big), rest}
+ end
end
diff --git a/test/fixtures/image.heic b/test/fixtures/image.heic
new file mode 100644
index 000000000..a8d77d7d8
Binary files /dev/null and b/test/fixtures/image.heic differ
diff --git a/test/fixtures/image.svg b/test/fixtures/image.svg
new file mode 100644
index 000000000..b2d7514e3
--- /dev/null
+++ b/test/fixtures/image.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1" viewBox="0 0 1 1">
+ <rect width="1" height="1" fill="#fff" />
+</svg>
diff --git a/test/fixtures/image.webp b/test/fixtures/image.webp
new file mode 100644
index 000000000..c78718cda
Binary files /dev/null and b/test/fixtures/image.webp differ
diff --git a/test/fixtures/image_with_caption-abstract.png b/test/fixtures/image_with_caption-abstract.png
new file mode 100644
index 000000000..4c332b7db
Binary files /dev/null and b/test/fixtures/image_with_caption-abstract.png differ
diff --git a/test/fixtures/image_with_imagedescription_exif.png b/test/fixtures/image_with_imagedescription_exif.png
new file mode 100644
index 000000000..e35091ee4
Binary files /dev/null and b/test/fixtures/image_with_imagedescription_exif.png differ
diff --git a/test/fixtures/image_with_imagedescription_xmp.gif b/test/fixtures/image_with_imagedescription_xmp.gif
new file mode 100644
index 000000000..ab28ac2b9
Binary files /dev/null and b/test/fixtures/image_with_imagedescription_xmp.gif differ
diff --git a/test/fixtures/strip_location_gps.heic b/test/fixtures/strip_location_gps.heic
new file mode 100644
index 000000000..64d9f0db0
Binary files /dev/null and b/test/fixtures/strip_location_gps.heic differ
diff --git a/test/fixtures/strip_location_gps.webp b/test/fixtures/strip_location_gps.webp
new file mode 100644
index 000000000..6b28b7720
Binary files /dev/null and b/test/fixtures/strip_location_gps.webp differ
diff --git a/test/pleroma/upload/filter/exiftool/read_description_test.exs b/test/pleroma/upload/filter/exiftool/read_description_test.exs
index 9a1bd61d7..372b79de4 100644
--- a/test/pleroma/upload/filter/exiftool/read_description_test.exs
+++ b/test/pleroma/upload/filter/exiftool/read_description_test.exs
@@ -1,144 +1,264 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Upload.Filter.Exiftool.ReadDescriptionTest do
use Pleroma.DataCase
alias Pleroma.Upload.Filter
@uploads %Pleroma.Upload{
name: "image_with_imagedescription_and_caption-abstract.jpg",
content_type: "image/jpeg",
path: Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
tempfile: Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
description: nil
}
test "keeps description when not empty" do
uploads = %Pleroma.Upload{
name: "image_with_imagedescription_and_caption-abstract.jpg",
content_type: "image/jpeg",
path: Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
tempfile:
Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
description: "Some description"
}
assert Filter.Exiftool.ReadDescription.filter(uploads) ==
{:ok, :noop}
end
test "otherwise returns ImageDescription when present" do
- uploads_after = %Pleroma.Upload{
- name: "image_with_imagedescription_and_caption-abstract.jpg",
- content_type: "image/jpeg",
- path: Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
- tempfile:
- Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
- description: "a descriptive white pixel"
- }
+ expected_description = exiftool_tag(@uploads.path, "ImageDescription")
+ assert is_binary(expected_description)
assert Filter.Exiftool.ReadDescription.filter(@uploads) ==
- {:ok, :filtered, uploads_after}
+ {:ok, :filtered, %{@uploads | description: expected_description}}
end
test "Ignores warnings" do
+ expected_description =
+ exiftool_tag(
+ fixture_path("image_with_imagedescription_and_caption-abstract_and_stray_data_after.png"),
+ "ImageDescription"
+ )
+
uploads = %Pleroma.Upload{
name: "image_with_imagedescription_and_caption-abstract_and_stray_data_after.png",
content_type: "image/png",
path:
Path.absname(
"test/fixtures/image_with_imagedescription_and_caption-abstract_and_stray_data_after.png"
),
tempfile:
Path.absname(
"test/fixtures/image_with_imagedescription_and_caption-abstract_and_stray_data_after.png"
)
}
- assert {:ok, :filtered, %{description: "a descriptive white pixel"}} =
+ assert {:ok, :filtered, %{description: ^expected_description}} =
Filter.Exiftool.ReadDescription.filter(uploads)
uploads = %Pleroma.Upload{
name: "image_with_stray_data_after.png",
content_type: "image/png",
path: Path.absname("test/fixtures/image_with_stray_data_after.png"),
tempfile: Path.absname("test/fixtures/image_with_stray_data_after.png")
}
assert {:ok, :filtered, %{description: nil}} = Filter.Exiftool.ReadDescription.filter(uploads)
end
test "otherwise returns iptc:Caption-Abstract when present" do
+ expected_caption =
+ exiftool_tag(fixture_path("image_with_caption-abstract.jpg"), "Caption-Abstract")
+
upload = %Pleroma.Upload{
name: "image_with_caption-abstract.jpg",
content_type: "image/jpeg",
path: Path.absname("test/fixtures/image_with_caption-abstract.jpg"),
tempfile: Path.absname("test/fixtures/image_with_caption-abstract.jpg"),
description: nil
}
- upload_after = %Pleroma.Upload{
- name: "image_with_caption-abstract.jpg",
- content_type: "image/jpeg",
- path: Path.absname("test/fixtures/image_with_caption-abstract.jpg"),
- tempfile: Path.absname("test/fixtures/image_with_caption-abstract.jpg"),
- description: "an abstract white pixel"
+ assert is_binary(expected_caption)
+
+ assert Filter.Exiftool.ReadDescription.filter(upload) ==
+ {:ok, :filtered, %{upload | description: expected_caption}}
+ end
+
+ test "reads ImageDescription from PNG EXIF chunk" do
+ expected_description =
+ exiftool_tag(fixture_path("image_with_imagedescription_exif.png"), "ImageDescription")
+
+ assert is_binary(expected_description)
+
+ upload = %Pleroma.Upload{
+ name: "image_with_imagedescription_exif.png",
+ content_type: "image/png",
+ path: fixture_path("image_with_imagedescription_exif.png"),
+ tempfile: fixture_path("image_with_imagedescription_exif.png"),
+ description: nil
+ }
+
+ assert Filter.Exiftool.ReadDescription.filter(upload) ==
+ {:ok, :filtered, %{upload | description: expected_description}}
+ end
+
+ test "reads iptc:Caption-Abstract from PNG zTXt profile" do
+ expected_caption =
+ exiftool_tag(fixture_path("image_with_caption-abstract.png"), "Caption-Abstract")
+
+ assert is_binary(expected_caption)
+
+ upload = %Pleroma.Upload{
+ name: "image_with_caption-abstract.png",
+ content_type: "image/png",
+ path: fixture_path("image_with_caption-abstract.png"),
+ tempfile: fixture_path("image_with_caption-abstract.png"),
+ description: nil
+ }
+
+ assert Filter.Exiftool.ReadDescription.filter(upload) ==
+ {:ok, :filtered, %{upload | description: expected_caption}}
+ end
+
+ test "reads ImageDescription from WebP EXIF chunk" do
+ expected_description =
+ exiftool_tag(fixture_path("strip_location_gps.webp"), "ImageDescription")
+
+ assert is_binary(expected_description)
+
+ upload = %Pleroma.Upload{
+ name: "strip_location_gps.webp",
+ content_type: "image/webp",
+ path: fixture_path("strip_location_gps.webp"),
+ tempfile: fixture_path("strip_location_gps.webp"),
+ description: nil
}
assert Filter.Exiftool.ReadDescription.filter(upload) ==
- {:ok, :filtered, upload_after}
+ {:ok, :filtered, %{upload | description: expected_description}}
+ end
+
+ test "reads ImageDescription from HEIC EXIF item" do
+ expected_description =
+ exiftool_tag(fixture_path("strip_location_gps.heic"), "ImageDescription")
+
+ assert is_binary(expected_description)
+
+ upload = %Pleroma.Upload{
+ name: "strip_location_gps.heic",
+ content_type: "image/heic",
+ path: fixture_path("strip_location_gps.heic"),
+ tempfile: fixture_path("strip_location_gps.heic"),
+ description: nil
+ }
+
+ assert Filter.Exiftool.ReadDescription.filter(upload) ==
+ {:ok, :filtered, %{upload | description: expected_description}}
+ end
+
+ test "reads ImageDescription from GIF XMP" do
+ expected_description =
+ exiftool_tag(fixture_path("image_with_imagedescription_xmp.gif"), "ImageDescription")
+
+ assert is_binary(expected_description)
+
+ upload = %Pleroma.Upload{
+ name: "image_with_imagedescription_xmp.gif",
+ content_type: "image/gif",
+ path: fixture_path("image_with_imagedescription_xmp.gif"),
+ tempfile: fixture_path("image_with_imagedescription_xmp.gif"),
+ description: nil
+ }
+
+ assert Filter.Exiftool.ReadDescription.filter(upload) ==
+ {:ok, :filtered, %{upload | description: expected_description}}
+ end
+
+ test "returns nil for unsupported image types" do
+ fixtures = [
+ {"image.gif", "image/gif"},
+ {"image.svg", "image/svg+xml"}
+ ]
+
+ for {name, content_type} <- fixtures do
+ upload = %Pleroma.Upload{
+ name: name,
+ content_type: content_type,
+ path: fixture_path(name),
+ tempfile: fixture_path(name),
+ description: nil
+ }
+
+ assert {:ok, :filtered, %{description: nil}} =
+ Filter.Exiftool.ReadDescription.filter(upload)
+ end
end
test "otherwise returns nil" do
uploads = %Pleroma.Upload{
name: "image_with_no_description.jpg",
content_type: "image/jpeg",
path: Path.absname("test/fixtures/image_with_no_description.jpg"),
tempfile: Path.absname("test/fixtures/image_with_no_description.jpg"),
description: nil
}
assert Filter.Exiftool.ReadDescription.filter(uploads) ==
{:ok, :filtered, uploads}
end
test "Return nil when image description from EXIF data exceeds the maximum length" do
clear_config([:instance, :description_limit], 5)
assert Filter.Exiftool.ReadDescription.filter(@uploads) ==
{:ok, :filtered, @uploads}
end
test "Ignores content with only whitespace" do
uploads = %Pleroma.Upload{
name: "non-existant.jpg",
content_type: "image/jpeg",
path:
Path.absname(
"test/fixtures/image_with_imagedescription_and_caption-abstract_whitespaces.jpg"
),
tempfile:
Path.absname(
"test/fixtures/image_with_imagedescription_and_caption-abstract_whitespaces.jpg"
),
description: nil
}
assert Filter.Exiftool.ReadDescription.filter(uploads) ==
{:ok, :filtered, uploads}
end
test "Return nil when image description from EXIF data can't be read" do
uploads = %Pleroma.Upload{
name: "non-existant.jpg",
content_type: "image/jpeg",
path: Path.absname("test/fixtures/non-existant.jpg"),
tempfile: Path.absname("test/fixtures/non-existant_tmp.jpg"),
description: nil
}
assert Filter.Exiftool.ReadDescription.filter(uploads) ==
{:ok, :filtered, uploads}
end
+
+ defp fixture_path(name), do: Path.absname(Path.join("test/fixtures", name))
+
+ defp exiftool_tag(file_path, tag) do
+ {output, 0} = System.cmd("exiftool", ["-m", "-s3", "-#{tag}", file_path])
+
+ output
+ |> String.trim()
+ |> case do
+ "" -> nil
+ value -> value
+ end
+ end
end
diff --git a/test/pleroma/upload/filter/exiftool/strip_location_test.exs b/test/pleroma/upload/filter/exiftool/strip_location_test.exs
index 1d6b0683d..c93923efc 100644
--- a/test/pleroma/upload/filter/exiftool/strip_location_test.exs
+++ b/test/pleroma/upload/filter/exiftool/strip_location_test.exs
@@ -1,85 +1,105 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Upload.Filter.Exiftool.StripLocationTest do
use Pleroma.DataCase, async: true
alias Pleroma.Upload.Filter
test "apply exiftool filter" do
- ~w{jpg png}
+ ~w{jpg png webp heic}
|> Enum.map(fn type ->
source = Path.absname("test/fixtures/strip_location_gps.#{type}")
tmp_path = copy_to_tmp(source)
+ content_type =
+ case type do
+ "png" -> "image/png"
+ "jpg" -> "image/jpeg"
+ "webp" -> "image/webp"
+ "heic" -> "image/heic"
+ end
+
upload = %Pleroma.Upload{
name: "image_with_GPS_data.#{type}",
- content_type: if(type == "png", do: "image/png", else: "image/jpeg"),
+ content_type: content_type,
path: source,
tempfile: tmp_path
}
assert Filter.Exiftool.StripLocation.filter(upload) == {:ok, :filtered}
assert exiftool_gps_tags(source) != ""
assert exiftool_gps_tags(tmp_path) == ""
end)
end
describe "ExifGpsStripper.strip_gps_data/2" do
- test "strips GPS data from JPEG and PNG" do
- for type <- ~w{jpg png} do
+ test "strips GPS data from JPEG, PNG, WebP and HEIC" do
+ for type <- ~w{jpg png webp heic} do
source_path = Path.absname("test/fixtures/strip_location_gps.#{type}")
tmp_path = copy_to_tmp(source_path)
assert exiftool_gps_tags(tmp_path) != ""
assert :ok = ExifGpsStripper.strip_gps_data(tmp_path, tmp_path)
assert exiftool_gps_tags(tmp_path) == ""
end
end
test "returns :noop for images without GPS metadata" do
- for fixture <- ["test/fixtures/image.jpg", "test/fixtures/image.png"] do
+ for fixture <- [
+ "test/fixtures/image.jpg",
+ "test/fixtures/image.png",
+ "test/fixtures/image.webp",
+ "test/fixtures/image.heic"
+ ] do
source_path = Path.absname(fixture)
original = File.read!(source_path)
tmp_path = copy_to_tmp(source_path)
assert exiftool_gps_tags(tmp_path) == ""
assert :noop = ExifGpsStripper.strip_gps_data(tmp_path, tmp_path)
assert File.read!(tmp_path) == original
end
end
end
- test "verify webp, heic, svg files are skipped" do
- uploads =
- ~w{webp heic svg svg+xml}
- |> Enum.map(fn type ->
- %Pleroma.Upload{
- name: "sample.#{type}",
- content_type: "image/#{type}"
- }
- end)
-
- uploads
- |> Enum.each(fn upload ->
+ test "verify unsupported image types are skipped/noop" do
+ fixtures = [
+ {"test/fixtures/image.svg", "image/svg+xml"},
+ {"test/fixtures/image.gif", "image/gif"}
+ ]
+
+ for {fixture, content_type} <- fixtures do
+ source_path = Path.absname(fixture)
+ tmp_path = copy_to_tmp(source_path)
+ original = File.read!(tmp_path)
+
+ upload = %Pleroma.Upload{
+ name: Path.basename(source_path),
+ content_type: content_type,
+ path: source_path,
+ tempfile: tmp_path
+ }
+
assert Filter.Exiftool.StripLocation.filter(upload) == {:ok, :noop}
- end)
+ assert File.read!(tmp_path) == original
+ end
end
defp copy_to_tmp(source_path) do
tmp_path =
Path.join(
System.tmp_dir!(),
"strip_location_#{System.unique_integer([:positive])}#{Path.extname(source_path)}"
)
File.cp!(source_path, tmp_path)
tmp_path
end
defp exiftool_gps_tags(file_path) do
{output, 0} = System.cmd("exiftool", ["-m", "-gps:all", file_path])
String.trim(output)
end
end

File Metadata

Mime Type
text/x-diff
Expires
Sat, Oct 31, 11:08 PM (20 h, 17 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1801891
Default Alt Text
(255 KB)

Event Timeline