Compare commits
2 Commits
feature/sa
...
feature/21
Author | SHA1 | Date | |
---|---|---|---|
|
2994ddb8e8 | ||
|
e8107cc96a |
@ -19,7 +19,7 @@
|
||||
#
|
||||
# You can give explicit globs or simply directories.
|
||||
# In the latter case `**/*.{ex,exs}` will be used.
|
||||
included: ["lib/", "src/", "web/", "apps/", "test/"],
|
||||
included: ["lib/", "src/", "web/", "apps/", "test/", "installer/pleroma"],
|
||||
excluded: [~r"/_build/", ~r"/deps/"]
|
||||
},
|
||||
#
|
||||
|
@ -1,3 +1,3 @@
|
||||
[
|
||||
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}", "priv/repo/migrations/*.exs", "priv/repo/optional_migrations/**/*.exs", "priv/scrubbers/*.ex"]
|
||||
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}", "priv/repo/migrations/*.exs", "priv/repo/optional_migrations/**/*.exs", "priv/scrubbers/*.ex", "installer/pleroma/**/*.{ex,exs}"]
|
||||
]
|
||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -13,6 +13,7 @@
|
||||
/doc
|
||||
/instance
|
||||
/priv/ssh_keys
|
||||
/installer/assets/node_modules/*
|
||||
|
||||
# Prevent committing custom emojis
|
||||
/priv/static/emoji/custom/*
|
||||
@ -56,4 +57,7 @@ pleroma.iml
|
||||
|
||||
# Editor temp files
|
||||
/*~
|
||||
/*#
|
||||
/*#
|
||||
|
||||
# local iex
|
||||
.iex.exs
|
||||
|
@ -6,7 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- The `application` metadata returned with statuses is no longer hardcoded. Apps that want to display these details will now have valid data for new posts after this change.
|
||||
- Config Versioning.
|
||||
- Web installer.
|
||||
|
||||
## Unreleased (Patch)
|
||||
|
||||
@ -51,7 +55,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
- Pleroma API: Reroute `/api/pleroma/*` to `/api/v1/pleroma/*`
|
||||
|
||||
</details>
|
||||
- Improved hashtag timeline performance (requires a background migration).
|
||||
- Improved hashtag timeline performance (requires a background migration).
|
||||
|
||||
### Added
|
||||
|
||||
@ -88,6 +92,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
- Mastodon API: `/api/v1/accounts/:id` & `/api/v1/mutes` endpoints accept `with_relationships` parameter and return filled `pleroma.relationship` field.
|
||||
- Mastodon API: Endpoint to remove a conversation (`DELETE /api/v1/conversations/:id`).
|
||||
- Mastodon API: `expires_in` in the scheduled post `params` field on `/api/v1/statuses` and `/api/v1/scheduled_statuses/:id` endpoints.
|
||||
- Admin API: (`GET /api/pleroma/admin/config/versions`) - endpoint to get list of config versions.
|
||||
- Admin API: (`GET /api/pleroma/admin/config/versions/rollback/:id`) - endpoint to rollback config to specific version.
|
||||
</details>
|
||||
|
||||
### Fixed
|
||||
|
@ -849,6 +849,25 @@ config :pleroma, ConcurrentLimiter, [
|
||||
{Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy, [max_running: 5, max_waiting: 5]}
|
||||
]
|
||||
|
||||
config :pleroma, Pleroma.InstallerWeb.Endpoint,
|
||||
http: [port: 4001],
|
||||
url: [host: "localhost"],
|
||||
secret_key_base: "izidwIWRlyFL/YjJBH672OWpSFXkMTk3KOgFL/Gj5vO5parwfHnoAQ8ZL+2Mn1SH",
|
||||
render_errors: [view: Pleroma.InstallerWeb.ErrorView, accepts: ~w(html)],
|
||||
protocol: "http",
|
||||
debug_errors: true,
|
||||
code_reloader: true,
|
||||
check_origin: false,
|
||||
node: [
|
||||
"node_modules/webpack/bin/webpack.js",
|
||||
"--mode",
|
||||
"development",
|
||||
"--watch-stdin",
|
||||
cd: Path.expand("../assets", __DIR__)
|
||||
]
|
||||
|
||||
config :pleroma, :installer, psql_cmd_args: ["sudo", ["-Hu", "postgres", "psql", "-c"]]
|
||||
|
||||
# Import environment specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
import_config "#{Mix.env()}.exs"
|
||||
|
@ -1062,65 +1062,63 @@ config :pleroma, :config_description, [
|
||||
description:
|
||||
"Where logs will be sent, :console - send logs to stdout, { ExSyslogger, :ex_syslogger } - to syslog, Quack.Logger - to Slack.",
|
||||
suggestions: [:console, {ExSyslogger, :ex_syslogger}, Quack.Logger]
|
||||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
group: :logger,
|
||||
type: :group,
|
||||
key: :ex_syslogger,
|
||||
label: "ExSyslogger",
|
||||
description: "ExSyslogger-related settings",
|
||||
children: [
|
||||
%{
|
||||
key: :level,
|
||||
type: {:dropdown, :atom},
|
||||
description: "Log level",
|
||||
suggestions: [:debug, :info, :warn, :error]
|
||||
},
|
||||
%{
|
||||
key: :ident,
|
||||
type: :string,
|
||||
description:
|
||||
"A string that's prepended to every message, and is typically set to the app name",
|
||||
suggestions: ["pleroma"]
|
||||
key: :ex_syslogger,
|
||||
type: :keyword,
|
||||
label: "ExSyslogger",
|
||||
description: "ExSyslogger-related settings",
|
||||
children: [
|
||||
%{
|
||||
key: :level,
|
||||
type: {:dropdown, :atom},
|
||||
description: "Log level",
|
||||
suggestions: [:debug, :info, :warn, :error]
|
||||
},
|
||||
%{
|
||||
key: :ident,
|
||||
type: :string,
|
||||
description:
|
||||
"A string that's prepended to every message, and is typically set to the app name",
|
||||
suggestions: ["pleroma"]
|
||||
},
|
||||
%{
|
||||
key: :format,
|
||||
type: :string,
|
||||
description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\"",
|
||||
suggestions: ["$metadata[$level] $message"]
|
||||
},
|
||||
%{
|
||||
key: :metadata,
|
||||
type: {:list, :atom},
|
||||
suggestions: [:request_id]
|
||||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
key: :format,
|
||||
type: :string,
|
||||
description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\"",
|
||||
suggestions: ["$metadata[$level] $message"]
|
||||
},
|
||||
%{
|
||||
key: :metadata,
|
||||
type: {:list, :atom},
|
||||
suggestions: [:request_id]
|
||||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
group: :logger,
|
||||
type: :group,
|
||||
key: :console,
|
||||
label: "Console Logger",
|
||||
description: "Console logger settings",
|
||||
children: [
|
||||
%{
|
||||
key: :level,
|
||||
type: {:dropdown, :atom},
|
||||
description: "Log level",
|
||||
suggestions: [:debug, :info, :warn, :error]
|
||||
},
|
||||
%{
|
||||
key: :format,
|
||||
type: :string,
|
||||
description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\"",
|
||||
suggestions: ["$metadata[$level] $message"]
|
||||
},
|
||||
%{
|
||||
key: :metadata,
|
||||
type: {:list, :atom},
|
||||
suggestions: [:request_id]
|
||||
key: :console,
|
||||
type: :keyword,
|
||||
label: "Console Logger",
|
||||
description: "Console logger settings",
|
||||
children: [
|
||||
%{
|
||||
key: :level,
|
||||
type: {:dropdown, :atom},
|
||||
description: "Log level",
|
||||
suggestions: [:debug, :info, :warn, :error]
|
||||
},
|
||||
%{
|
||||
key: :format,
|
||||
type: :string,
|
||||
description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\"",
|
||||
suggestions: ["$metadata[$level] $message"]
|
||||
},
|
||||
%{
|
||||
key: :metadata,
|
||||
type: {:list, :atom},
|
||||
suggestions: [:request_id]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -1840,19 +1838,13 @@ config :pleroma, :config_description, [
|
||||
},
|
||||
%{
|
||||
group: :pleroma,
|
||||
key: :admin_token,
|
||||
label: "Pleroma Admin Token",
|
||||
type: :group,
|
||||
type: :string,
|
||||
description:
|
||||
"Allows setting a token that can be used to authenticate requests with admin privileges without a normal user account token. Append the `admin_token` parameter to requests to utilize it. (Please reconsider using HTTP Basic Auth or OAuth-based authentication if possible)",
|
||||
children: [
|
||||
%{
|
||||
key: :admin_token,
|
||||
type: :string,
|
||||
description: "Admin token",
|
||||
suggestions: [
|
||||
"Please use a high entropy string or UUID"
|
||||
]
|
||||
}
|
||||
suggestions: [
|
||||
"Please use a high entropy string or UUID"
|
||||
]
|
||||
},
|
||||
%{
|
||||
@ -2153,16 +2145,11 @@ config :pleroma, :config_description, [
|
||||
},
|
||||
%{
|
||||
group: :pleroma,
|
||||
key: Pleroma.Web.Auth.Authenticator,
|
||||
label: "Pleroma Authenticator",
|
||||
type: :group,
|
||||
type: :module,
|
||||
description: "Authenticator",
|
||||
children: [
|
||||
%{
|
||||
key: Pleroma.Web.Auth.Authenticator,
|
||||
type: :module,
|
||||
suggestions: [Pleroma.Web.Auth.PleromaAuthenticator, Pleroma.Web.Auth.LDAPAuthenticator]
|
||||
}
|
||||
]
|
||||
suggestions: [Pleroma.Web.Auth.PleromaAuthenticator, Pleroma.Web.Auth.LDAPAuthenticator]
|
||||
},
|
||||
%{
|
||||
group: :pleroma,
|
||||
|
@ -62,5 +62,7 @@ else
|
||||
)
|
||||
end
|
||||
|
||||
config :pleroma, :installer, psql_cmd_args: ["psql", ["-c"]]
|
||||
|
||||
if File.exists?("./config/dev.exported_from_db.secret.exs"),
|
||||
do: import_config("dev.exported_from_db.secret.exs")
|
||||
|
@ -63,7 +63,7 @@ config :logger, :ex_syslogger, level: :info
|
||||
|
||||
# Finally import the config/prod.secret.exs
|
||||
# which should be versioned separately.
|
||||
import_config "prod.secret.exs"
|
||||
if File.exists?("./config/prod.secret.exs"), do: import_config("prod.secret.exs")
|
||||
|
||||
if File.exists?("./config/prod.exported_from_db.secret.exs"),
|
||||
do: import_config("prod.exported_from_db.secret.exs")
|
||||
|
@ -7,6 +7,11 @@ config :pleroma, Pleroma.Web.Endpoint,
|
||||
url: [port: 4001],
|
||||
server: true
|
||||
|
||||
config :pleroma, Pleroma.InstallerWeb.Endpoint,
|
||||
http: [port: 4002],
|
||||
url: [port: 4002],
|
||||
server: true
|
||||
|
||||
# Disable captha for tests
|
||||
config :pleroma, Pleroma.Captcha,
|
||||
# It should not be enabled for automatic tests
|
||||
@ -46,7 +51,7 @@ config :pleroma, Pleroma.Repo,
|
||||
username: "postgres",
|
||||
password: "postgres",
|
||||
database: "pleroma_test",
|
||||
hostname: System.get_env("DB_HOST") || "localhost",
|
||||
hostname: System.get_env("DB_HOST", "localhost"),
|
||||
pool: Ecto.Adapters.SQL.Sandbox,
|
||||
pool_size: 50
|
||||
|
||||
@ -91,7 +96,9 @@ config :pleroma, Pleroma.ScheduledActivity,
|
||||
total_user_limit: 3,
|
||||
enabled: false
|
||||
|
||||
config :pleroma, :rate_limit, %{}
|
||||
# Hack to drop default settings from `config.exs`, because keywords are deeply merged, so there is no other way to do it.
|
||||
config :pleroma, :rate_limit, nil
|
||||
config :pleroma, :rate_limit, []
|
||||
|
||||
config :pleroma, :http_security, report_uri: "https://endpoint.com"
|
||||
|
||||
@ -133,6 +140,10 @@ config :pleroma, :side_effects,
|
||||
ap_streamer: Pleroma.Web.ActivityPub.ActivityPubMock,
|
||||
logger: Pleroma.LoggerMock
|
||||
|
||||
config :pleroma, :installer,
|
||||
repo: Pleroma.Installer.InstallerRepoMock,
|
||||
file: Pleroma.Installer.FileMock
|
||||
|
||||
if File.exists?("./config/test.secret.exs") do
|
||||
import_config "test.secret.exs"
|
||||
else
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
||||
|
||||
## Transfer config from file to DB.
|
||||
## Transfer config from file to DB
|
||||
|
||||
!!! note
|
||||
You need to add the following to your config before executing this command:
|
||||
@ -150,4 +150,24 @@ This forcibly removes all saved values in the database.
|
||||
|
||||
```sh
|
||||
mix pleroma.config [--force] reset
|
||||
|
||||
## Rollback config version
|
||||
|
||||
!!! note
|
||||
You need to add the following to your config before executing this command:
|
||||
|
||||
```elixir
|
||||
config :pleroma, configurable_from_database: true
|
||||
```
|
||||
|
||||
Rollback will restore last backup by default. If you want to restore older version use `-s` parameter.
|
||||
|
||||
=== "OTP"
|
||||
```sh
|
||||
./bin/pleroma_ctl config rollback [-s 2]
|
||||
```
|
||||
|
||||
=== "From Source"
|
||||
```sh
|
||||
mix pleroma.config rollback [-s 2]
|
||||
```
|
||||
|
@ -893,10 +893,10 @@ Status: 404
|
||||
|
||||
**Only works when configuration from database is enabled.**
|
||||
|
||||
- Params: none
|
||||
- Response:
|
||||
- On failure:
|
||||
- 400 Bad Request `"To use this endpoint you need to enable configuration from database."`
|
||||
* Params: none
|
||||
* Response:
|
||||
* On failure:
|
||||
* 400 Bad Request `"To use this endpoint you need to enable configuration from database."`
|
||||
|
||||
```json
|
||||
{}
|
||||
@ -906,9 +906,10 @@ Status: 404
|
||||
|
||||
### Returns the flag whether the pleroma should be restarted
|
||||
|
||||
- Params: none
|
||||
- Response:
|
||||
- `need_reboot` - boolean
|
||||
* Params: none
|
||||
* Response:
|
||||
* `need_reboot` - boolean
|
||||
|
||||
```json
|
||||
{
|
||||
"need_reboot": false
|
||||
@ -917,17 +918,17 @@ Status: 404
|
||||
|
||||
## `GET /api/v1/pleroma/admin/config`
|
||||
|
||||
### Get list of merged default settings with saved in database.
|
||||
### Get list of merged default settings with saved in database
|
||||
|
||||
*If `need_reboot` is `true`, instance must be restarted, so reboot time settings can take effect.*
|
||||
|
||||
**Only works when configuration from database is enabled.**
|
||||
|
||||
- Params:
|
||||
- `only_db`: true (*optional*, get only saved in database settings)
|
||||
- Response:
|
||||
- On failure:
|
||||
- 400 Bad Request `"To use this endpoint you need to enable configuration from database."`
|
||||
* Params:
|
||||
* `only_db`: true (*optional*, get only saved in database settings)
|
||||
* Response:
|
||||
* On failure:
|
||||
* 400 Bad Request `"To use this endpoint you need to enable configuration from database."`
|
||||
|
||||
```json
|
||||
{
|
||||
@ -952,72 +953,71 @@ Status: 404
|
||||
|
||||
Some modifications are necessary to save the config settings correctly:
|
||||
|
||||
- strings which start with `Pleroma.`, `Phoenix.`, `Tesla.` or strings like `Oban`, `Ueberauth` will be converted to modules;
|
||||
```
|
||||
"Pleroma.Upload" -> Pleroma.Upload
|
||||
"Oban" -> Oban
|
||||
```
|
||||
- strings starting with `:` will be converted to atoms;
|
||||
```
|
||||
":pleroma" -> :pleroma
|
||||
```
|
||||
- objects with `tuple` key and array value will be converted to tuples;
|
||||
```
|
||||
{"tuple": ["string", "Pleroma.Upload", []]} -> {"string", Pleroma.Upload, []}
|
||||
```
|
||||
- arrays with *tuple objects* will be converted to keywords;
|
||||
```
|
||||
[{"tuple": [":key1", "value"]}, {"tuple": [":key2", "value"]}] -> [key1: "value", key2: "value"]
|
||||
```
|
||||
* strings which start with `Pleroma.`, `Phoenix.`, `Tesla.` or strings like `Oban`, `Ueberauth` will be converted to modules
|
||||
* `"Pleroma.Upload"` -> `Pleroma.Upload`
|
||||
* `"Oban"` -> `Oban`
|
||||
* strings starting with `:` will be converted to atoms
|
||||
* `":pleroma"` -> `:pleroma`
|
||||
* objects with `tuple` key and array value will be converted to tuples
|
||||
* `{"tuple": ["string", "Pleroma.Upload", []]}` -> `{"string", Pleroma.Upload, []}`
|
||||
* arrays with *tuple objects* will be converted to keywords
|
||||
* `[{"tuple": [":key1", "value"]}, {"tuple": [":key2", "value"]}]` -> `[key1: "value", key2: "value"]`
|
||||
|
||||
Most of the settings will be applied in `runtime`, this means that you don't need to restart the instance. But some settings are applied in `compile time` and require a reboot of the instance, such as:
|
||||
- all settings inside these keys:
|
||||
- `:hackney_pools`
|
||||
- `:connections_pool`
|
||||
- `:pools`
|
||||
- `:chat`
|
||||
- partially settings inside these keys:
|
||||
- `:seconds_valid` in `Pleroma.Captcha`
|
||||
- `:proxy_remote` in `Pleroma.Upload`
|
||||
- `:upload_limit` in `:instance`
|
||||
Most of the settings will be applied in `runtime`, this means that changes will be applied immediately. But some settings are applied on `startup time` and will take effect after restart of the pleroma parts, such as:
|
||||
|
||||
- Params:
|
||||
- `configs` - array of config objects
|
||||
- config object params:
|
||||
- `group` - string (**required**)
|
||||
- `key` - string (**required**)
|
||||
- `value` - string, [], {} or {"tuple": []} (**required**)
|
||||
- `delete` - true (*optional*, if setting must be deleted)
|
||||
- `subkeys` - array of strings (*optional*, only works when `delete=true` parameter is passed, otherwise will be ignored)
|
||||
* all settings inside these keys
|
||||
* `:chat`
|
||||
* `Oban`
|
||||
* `:rate_limit`
|
||||
* `:streamer`
|
||||
* `:pools`
|
||||
* `:connections_pool`
|
||||
* `:hackney_pools`
|
||||
* `:gopher`
|
||||
* `:eshhd`
|
||||
* `:ex_aws`
|
||||
* partially settings inside these keys:
|
||||
* `:seconds_valid` in `Pleroma.Captcha`
|
||||
* `:proxy_remote` in `Pleroma.Upload`
|
||||
* `:upload_limit` in `:instance`
|
||||
* `:enabled` in `:fed_sockets`
|
||||
|
||||
*When a value have several nested settings, you can delete only some nested settings by passing a parameter `subkeys`, without deleting all settings by key.*
|
||||
```
|
||||
[subkey: val1, subkey2: val2, subkey3: val3] \\ initial value
|
||||
{"group": ":pleroma", "key": "some_key", "delete": true, "subkeys": [":subkey", ":subkey3"]} \\ passing json for deletion
|
||||
[subkey2: val2] \\ value after deletion
|
||||
```
|
||||
* Params:
|
||||
* `configs` - array of config objects
|
||||
* config object params:
|
||||
* `group` - string (**required**)
|
||||
* `key` - string (**required**)
|
||||
* `value` - string, [], {} or {"tuple": []} (**required**)
|
||||
* `delete` - true (*optional*, if setting must be deleted)
|
||||
* `subkeys` - array of strings (*optional*, only works when `delete=true` parameter is passed, otherwise will be ignored)
|
||||
|
||||
*Most of the settings can be partially updated through merge old values with new values, except settings value of which is list or is not keyword.*
|
||||
#### Partial deletion
|
||||
|
||||
Keys inside value can be partially deleted by passing `subkeys` parameter. If after partial deleting an empty list remains, then the entire setting will be deleted.
|
||||
|
||||
Example:
|
||||
|
||||
Example of setting without keyword in value:
|
||||
```elixir
|
||||
config :tesla, :adapter, Tesla.Adapter.Hackney
|
||||
# initial value
|
||||
[subkey: :val1, subkey2: :val2, subkey3: :val3]
|
||||
```
|
||||
|
||||
```json
|
||||
// config object for deletion
|
||||
{"group": ":pleroma", "key": "some_key", "delete": true, "subkeys": [":subkey", ":subkey3"]}
|
||||
```
|
||||
|
||||
List of settings which support only full update by key:
|
||||
```elixir
|
||||
@full_key_update [
|
||||
{:pleroma, :ecto_repos},
|
||||
{:quack, :meta},
|
||||
{:mime, :types},
|
||||
{:cors_plug, [:max_age, :methods, :expose, :headers]},
|
||||
{:auto_linker, :opts},
|
||||
{:swarm, :node_blacklist},
|
||||
{:logger, :backends}
|
||||
]
|
||||
# value after deletion
|
||||
[subkey2: :val2]
|
||||
```
|
||||
|
||||
List of settings which support only full update by subkey:
|
||||
#### Partial update
|
||||
|
||||
Most settings can be partially updated: new values will be merged with existing ones.
|
||||
|
||||
The following settings are exceptions and should be fully updated:
|
||||
|
||||
```elixir
|
||||
@full_subkey_update [
|
||||
{:pleroma, :assets, :mascots},
|
||||
@ -1028,23 +1028,25 @@ List of settings which support only full update by subkey:
|
||||
]
|
||||
```
|
||||
|
||||
*Settings without explicit key must be sended in separate config object params.*
|
||||
#### Settings without explicit keys
|
||||
|
||||
Settings without explicit key must be sended in one config object with null value as key.
|
||||
|
||||
```elixir
|
||||
config :quack,
|
||||
level: :debug,
|
||||
meta: [:all],
|
||||
...
|
||||
meta: [:all]
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"configs": [
|
||||
{"group": ":quack", "key": ":level", "value": ":debug"},
|
||||
{"group": ":quack", "key": ":meta", "value": [":all"]},
|
||||
...
|
||||
{"group": ":quack", "key": null, "value": [{"tuple": [":level", ":debug"]}, {"tuple": [":meta", ":all"]}]}
|
||||
]
|
||||
}
|
||||
```
|
||||
- Request:
|
||||
|
||||
* Request:
|
||||
|
||||
```json
|
||||
{
|
||||
@ -1075,9 +1077,10 @@ config :quack,
|
||||
}
|
||||
```
|
||||
|
||||
- Response:
|
||||
- On failure:
|
||||
- 400 Bad Request `"To use this endpoint you need to enable configuration from database."`
|
||||
* Response:
|
||||
* On failure:
|
||||
* 400 Bad Request `"To use this endpoint you need to enable configuration from database."`
|
||||
|
||||
```json
|
||||
{
|
||||
"configs": [
|
||||
@ -1091,9 +1094,10 @@ config :quack,
|
||||
}
|
||||
```
|
||||
|
||||
## ` GET /api/v1/pleroma/admin/config/descriptions`
|
||||
## `GET /api/v1/pleroma/admin/config/descriptions`
|
||||
|
||||
### Get JSON with config descriptions
|
||||
|
||||
### Get JSON with config descriptions.
|
||||
Loads json generated from `config/descriptions.exs`.
|
||||
|
||||
- Params: none
|
||||
@ -1124,6 +1128,37 @@ Loads json generated from `config/descriptions.exs`.
|
||||
}]
|
||||
```
|
||||
|
||||
## `GET /api/v1/pleroma/admin/config/versions/rollback/:id`
|
||||
|
||||
### Rollback config changes for a given version
|
||||
|
||||
- Params:
|
||||
- `id` - version id for rollback
|
||||
- Response:
|
||||
- On success: `204`, empty response
|
||||
- On failure:
|
||||
- 400 Bad Request `"To use this endpoint you need to enable configuration from database."` or endpoint error
|
||||
- 404 Not found
|
||||
|
||||
## `GET /api/v1/pleroma/admin/config/versions`
|
||||
|
||||
### Get list of config versions
|
||||
|
||||
- Params: none
|
||||
- Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"versions": [
|
||||
{
|
||||
"id": 1,
|
||||
"current": true,
|
||||
"inserted_at": "2020-04-21T15:11:46.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## `GET /api/v1/pleroma/admin/moderation_log`
|
||||
|
||||
### Get moderation log
|
||||
|
@ -16,6 +16,6 @@
|
||||
|
||||
* With non-OAuth authentication ([HTTP Basic Authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) or HTTP header- or params-provided auth), OAuth scopes check is _not_ performed for any action (since password is provided during the auth, requester is able to obtain a token with full permissions anyways); auth plugs invoke `Pleroma.Helpers.AuthHelper.skip_oauth(conn)` in this case.
|
||||
|
||||
## Auth-related configuration, OAuth consumer mode etc.
|
||||
### Auth-related configuration, OAuth consumer mode etc.
|
||||
|
||||
See `Authentication` section of [the configuration cheatsheet](../configuration/cheatsheet.md#authentication).
|
||||
|
40
docs/development/config_versioning.md
Normal file
40
docs/development/config_versioning.md
Normal file
@ -0,0 +1,40 @@
|
||||
# Config versioning
|
||||
|
||||
Database configuration supports simple versioning. Every change (list of changes or only one change) through adminFE creates new version with backup from config table. It is possible to do rollback on N steps (1 by default). Rollback will recreate `config` table from backup.
|
||||
|
||||
**IMPORTANT** Destructive operations with `Pleroma.ConfigDB` and `Pleroma.Config.Version` must be processed through `Pleroma.Config.Versioning` module for correct versioning work, especially migration changes.
|
||||
|
||||
Example:
|
||||
|
||||
* new config setting is added directly using `Pleroma.ConfigDB` module
|
||||
* user is doing rollback and setting is lost
|
||||
|
||||
## Creating new version
|
||||
|
||||
Creating new version is done with `Pleroma.Config.Versioning.new_version/1`, which accepts list of changes. Changes can include adding/updating/deleting operations in `config` table at the same time.
|
||||
|
||||
Process of creating new version:
|
||||
|
||||
* saving config changes in `config` table
|
||||
* saving new version with current configs
|
||||
* `backup` - keyword with all configs from `config` table (binary)
|
||||
* `current` - flag, which marks current version (boolean)
|
||||
|
||||
## Version rollback
|
||||
|
||||
Version control also supports a simple N steps back mechanism.
|
||||
|
||||
Rollback process:
|
||||
|
||||
* cleaning `config` table
|
||||
* splitting `backup` field into separate settings and inserting them into `config` table
|
||||
* removing subsequent versions
|
||||
|
||||
## Config migrations
|
||||
|
||||
Sometimes it becomes necessary to make changes to the configuration, which can be stored in the user's database. Config versioning makes this process more complicated, as we also must update this setting in versions backups.
|
||||
|
||||
Versioning module contains two functions for migrations:
|
||||
|
||||
* `Pleroma.Config.Versioning.migrate_namespace/2` - for simple renaming, e.g. group or key of the setting must be renamed.
|
||||
* `Pleroma.Config.Versioning.migrate_configs_and_versions/2` - abstract function for more complex migrations. Accepts two functions, the first one to make changes with configs, another to make changes with version backups.
|
3
installer/assets/css/app.css
Normal file
3
installer/assets/css/app.css
Normal file
@ -0,0 +1,3 @@
|
||||
/* This file is for your main application css. */
|
||||
|
||||
@import "./phoenix.css";
|
134
installer/assets/css/phoenix.css
Normal file
134
installer/assets/css/phoenix.css
Normal file
File diff suppressed because one or more lines are too long
59
installer/assets/js/app.js
Normal file
59
installer/assets/js/app.js
Normal file
@ -0,0 +1,59 @@
|
||||
// We need to import the CSS so that webpack will load it.
|
||||
// The MiniCssExtractPlugin is used to separate it out into
|
||||
// its own CSS file.
|
||||
import css from "../css/app.css"
|
||||
|
||||
// webpack automatically bundles all modules in your
|
||||
// entry points. Those entry points can be configured
|
||||
// in "webpack.config.js".
|
||||
//
|
||||
// Import dependencies
|
||||
//
|
||||
import "phoenix_html"
|
||||
import "phoenix"
|
||||
import { Ajax } from "phoenix"
|
||||
|
||||
// Import local files
|
||||
//
|
||||
// Local files can be imported directly using relative paths, for example:
|
||||
// import socket from "./socket"
|
||||
window.onload = function () {
|
||||
let endpointUrlEl = document.getElementById('config_form_endpoint_url')
|
||||
if (endpointUrlEl) {
|
||||
endpointUrlEl.addEventListener('change', function (evt) {
|
||||
let endpointUrl = new URL(endpointUrlEl.value);
|
||||
|
||||
let adminEmailEl = document.getElementById('config_form_instance_email');
|
||||
if (adminEmailEl.value == '') {
|
||||
adminEmailEl.value = 'admin@' + endpointUrl.hostname;
|
||||
}
|
||||
|
||||
let adminUserEmailEl = document.getElementById('config_form_admin_email');
|
||||
if (adminUserEmailEl.value == '') {
|
||||
adminUserEmailEl.value = 'admin@' + endpointUrl.hostname;
|
||||
}
|
||||
|
||||
let notifyEmailEl = document.getElementById('config_form_instance_notify_email');
|
||||
if (notifyEmailEl.value == '') {
|
||||
notifyEmailEl.value = 'no-reply@' + endpointUrl.hostname;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let migrations = document.getElementById('migrations')
|
||||
|
||||
if (migrations) {
|
||||
Ajax.request("GET", "/run_migrations", "application/json", "", 20000, show_error, (resp) => {
|
||||
if (resp == "ok") {
|
||||
window.location = "/config";
|
||||
} else {
|
||||
show_error();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function show_error() {
|
||||
let errorEl = document.getElementById('error');
|
||||
errorEl.style.visibility = 'visible';
|
||||
}
|
14977
installer/assets/package-lock.json
generated
Normal file
14977
installer/assets/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
installer/assets/package.json
Normal file
25
installer/assets/package.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"repository": {},
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"deploy": "webpack --mode production",
|
||||
"watch": "webpack --mode development --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.0",
|
||||
"phoenix": "file:../../deps/phoenix",
|
||||
"phoenix_html": "file:../../deps/phoenix_html"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"babel-loader": "^8.0.0",
|
||||
"copy-webpack-plugin": "^5.1.1",
|
||||
"css-loader": "^3.4.2",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.1",
|
||||
"terser-webpack-plugin": "^2.3.2",
|
||||
"webpack": "4.41.5",
|
||||
"webpack-cli": "^3.3.2"
|
||||
}
|
||||
}
|
BIN
installer/assets/static/images/favicon.png
Normal file
BIN
installer/assets/static/images/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
71
installer/assets/static/images/logo.svg
Normal file
71
installer/assets/static/images/logo.svg
Normal file
@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="svg4485"
|
||||
width="512"
|
||||
height="512"
|
||||
viewBox="0 0 512 512"
|
||||
sodipodi:docname="logo.svg"
|
||||
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
|
||||
<metadata
|
||||
id="metadata4491">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs4489" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1274"
|
||||
inkscape:window-height="1410"
|
||||
id="namedview4487"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.2636719"
|
||||
inkscape:cx="305.99333"
|
||||
inkscape:cy="304.30809"
|
||||
inkscape:window-x="1280"
|
||||
inkscape:window-y="22"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="g4612"
|
||||
inkscape:document-rotation="0" />
|
||||
<g
|
||||
id="g4612">
|
||||
<g
|
||||
id="g850"
|
||||
transform="matrix(0.99659595,0,0,0.99659595,0.37313949,0.87143746)">
|
||||
<path
|
||||
style="opacity:1;fill:#fba457;fill-opacity:1;stroke:#009bff;stroke-width:0;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.175879"
|
||||
d="m 194.75841,124.65165 a 20.449443,20.449443 0 0 0 -20.44944,20.44945 v 242.24725 h 65.28091 v -262.6967 z"
|
||||
id="path4497" />
|
||||
<path
|
||||
style="fill:#fba457;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 272.6236,124.65165 V 256 h 45.61799 a 20.449443,20.449443 0 0 0 20.44944,-20.44945 v -110.8989 z"
|
||||
id="path4516" />
|
||||
<path
|
||||
style="opacity:1;fill:#fba457;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 272.6236,322.06744 v 65.28091 h 45.61799 a 20.449443,20.449443 0 0 0 20.44944,-20.44945 v -44.83146 z"
|
||||
id="path4516-5" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
41
installer/assets/webpack.config.js
Normal file
41
installer/assets/webpack.config.js
Normal file
@ -0,0 +1,41 @@
|
||||
const path = require('path');
|
||||
const glob = require('glob');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
|
||||
module.exports = (env, options) => ({
|
||||
optimization: {
|
||||
minimizer: [
|
||||
new TerserPlugin({ cache: true, parallel: true, sourceMap: false }),
|
||||
new OptimizeCSSAssetsPlugin({})
|
||||
]
|
||||
},
|
||||
entry: {
|
||||
'./js/app.js': glob.sync('./vendor/**/*.js').concat(['./js/app.js'])
|
||||
},
|
||||
output: {
|
||||
filename: 'app.js',
|
||||
path: path.resolve(__dirname, '../../priv/static/installer/js')
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'babel-loader'
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [MiniCssExtractPlugin.loader, 'css-loader']
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin({ filename: '../css/app.css' }),
|
||||
new CopyWebpackPlugin([{ from: 'static/', to: '../' }])
|
||||
]
|
||||
});
|
10
installer/pleroma/installer/file.ex
Normal file
10
installer/pleroma/installer/file.ex
Normal file
@ -0,0 +1,10 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Installer.File do
|
||||
@callback write(Path.t(), iodata()) :: :ok | {:error, :file.posix()}
|
||||
@callback write(Path.t(), iodata(), [atom()]) :: :ok | {:error, :file.posix()}
|
||||
|
||||
defdelegate write(path, content, modes \\ []), to: File
|
||||
end
|
147
installer/pleroma/installer/installer_repo.ex
Normal file
147
installer/pleroma/installer/installer_repo.ex
Normal file
@ -0,0 +1,147 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Installer.InstallerRepo do
|
||||
require Logger
|
||||
|
||||
alias Pleroma.Repo
|
||||
|
||||
@dynamic_repo :installer
|
||||
|
||||
@callback check_database(keyword()) :: :ok | {:error, term()}
|
||||
@callback create_database(keyword()) :: :ok | {:error, term()}
|
||||
@callback stop() :: :ok
|
||||
@callback run_migrations(keyword(), [Path.t()]) :: :ok | {:error, :migrations_error}
|
||||
@callback start_repo(keyword()) :: {:ok, pid()} | {:error, term()}
|
||||
|
||||
@spec dynamic_repo() :: atom()
|
||||
def dynamic_repo, do: @dynamic_repo
|
||||
|
||||
@spec check_database(keyword()) :: :ok | {:error, term()}
|
||||
def check_database(credentials) do
|
||||
credentials = Keyword.put(credentials, :name, @dynamic_repo)
|
||||
|
||||
with {:ok, _} <- start_repo(credentials),
|
||||
{:ok, _} <- check_connection(),
|
||||
:ok <- check_extensions(credentials[:rum_enabled]) do
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
@spec create_database(keyword()) :: :ok | {:error, term()}
|
||||
def create_database(credentials) do
|
||||
credentials = Keyword.put(credentials, :name, @dynamic_repo)
|
||||
|
||||
maintenance_credentials = Keyword.merge(credentials, username: "postgres", password: "")
|
||||
|
||||
with :ok <- storage_up(maintenance_credentials),
|
||||
{:ok, _} <- start_repo(maintenance_credentials),
|
||||
{:ok, _} <- check_connection() do
|
||||
queries = [
|
||||
"CREATE USER #{credentials[:username]} WITH ENCRYPTED PASSWORD '#{credentials[:password]}';",
|
||||
"ALTER DATABASE #{credentials[:database]} OWNER TO #{credentials[:username]};",
|
||||
"CREATE EXTENSION IF NOT EXISTS citext;",
|
||||
"CREATE EXTENSION IF NOT EXISTS pg_trgm;",
|
||||
"CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";"
|
||||
]
|
||||
|
||||
queries =
|
||||
if credentials[:rum_enabled] do
|
||||
queries ++ ["CREATE EXTENSION IF NOT EXISTS rum;"]
|
||||
else
|
||||
queries
|
||||
end
|
||||
|
||||
Enum.reduce_while(queries, :ok, fn query, acc ->
|
||||
case Repo.query(query) do
|
||||
{:ok, _} -> {:cont, acc}
|
||||
error -> {:halt, error}
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defp storage_up(credentials) do
|
||||
with {:error, _} = error <- Ecto.Adapters.Postgres.storage_up(credentials) do
|
||||
Logger.error("Can't create repo due to error: #{inspect(error)}")
|
||||
{:error, :create_repo}
|
||||
end
|
||||
end
|
||||
|
||||
@spec run_migrations(keyword(), [Path.t()]) ::
|
||||
:ok | {:error, :migrations_error} | {:error, term()}
|
||||
def run_migrations(credentials, paths) do
|
||||
case start_repo(credentials) do
|
||||
{:ok, _} ->
|
||||
if Ecto.Migrator.run(Repo, paths, :up, all: true, dynamic_repo: @dynamic_repo) != [] do
|
||||
:ok
|
||||
else
|
||||
Logger.error("Not all migrations were applied.")
|
||||
{:error, :migrations_error}
|
||||
end
|
||||
|
||||
error ->
|
||||
Logger.error("Can't run migratios due to error: #{inspect(error)}")
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
@spec stop() :: :ok
|
||||
def stop do
|
||||
@dynamic_repo
|
||||
|> Process.whereis()
|
||||
|> case do
|
||||
repo when is_pid(repo) -> Supervisor.stop(repo)
|
||||
_ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
@spec start_repo(keyword()) :: {:ok, pid()} | {:error, term()}
|
||||
def start_repo(credentials) when is_list(credentials) do
|
||||
credentials = Keyword.put(credentials, :name, @dynamic_repo)
|
||||
|
||||
case Repo.start_link(credentials) do
|
||||
{:ok, pid} = result ->
|
||||
Repo.put_dynamic_repo(pid)
|
||||
result
|
||||
|
||||
error ->
|
||||
Logger.error("Can't start repo due to error: #{inspect(error)}")
|
||||
{:error, :installer_repo_start}
|
||||
end
|
||||
end
|
||||
|
||||
defp check_connection do
|
||||
with {:error, _} = error <- Repo.query("SELECT 1") do
|
||||
Logger.error("Can't connect to database due to error #{inspect(error)}")
|
||||
{:error, :query_execution}
|
||||
end
|
||||
end
|
||||
|
||||
defp check_extensions(rum_enabled?) do
|
||||
default = ["citext", "pg_trgm", "uuid-ossp"]
|
||||
|
||||
required = if rum_enabled?, do: ["rum" | default], else: default
|
||||
|
||||
with {:ok, %{rows: extensions}} <- Repo.query("SELECT pg_available_extensions();") do
|
||||
extensions = Enum.map(extensions, fn [{name, _, _}] -> name end)
|
||||
|
||||
not_installed =
|
||||
Enum.reduce(required, [], fn ext, acc ->
|
||||
if ext in extensions do
|
||||
acc
|
||||
else
|
||||
[ext | acc]
|
||||
end
|
||||
end)
|
||||
|
||||
if not_installed == [] do
|
||||
:ok
|
||||
else
|
||||
Logger.error("These extensions are not installed: #{Enum.join(not_installed, ",")}")
|
||||
{:error, :extensions_not_installed}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
47
installer/pleroma/installer_web.ex
Normal file
47
installer/pleroma/installer_web.ex
Normal file
@ -0,0 +1,47 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.InstallerWeb do
|
||||
def controller do
|
||||
quote do
|
||||
use Phoenix.Controller, namespace: Pleroma.InstallerWeb
|
||||
|
||||
import Plug.Conn
|
||||
alias Pleroma.InstallerWeb.Router.Helpers, as: Routes
|
||||
end
|
||||
end
|
||||
|
||||
def view do
|
||||
quote do
|
||||
use Phoenix.View,
|
||||
root: "installer/pleroma/installer_web/templates",
|
||||
namespace: Pleroma.InstallerWeb
|
||||
|
||||
import Phoenix.Controller,
|
||||
only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1]
|
||||
|
||||
use Phoenix.HTML
|
||||
|
||||
import Pleroma.InstallerWeb.ErrorHelpers
|
||||
import Pleroma.Web.Gettext
|
||||
|
||||
alias Pleroma.InstallerWeb.Router.Helpers, as: Routes
|
||||
end
|
||||
end
|
||||
|
||||
def router do
|
||||
quote do
|
||||
use Phoenix.Router
|
||||
import Plug.Conn
|
||||
import Phoenix.Controller
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
When used, dispatch to the appropriate controller/view/etc.
|
||||
"""
|
||||
defmacro __using__(which) when is_atom(which) do
|
||||
apply(__MODULE__, which, [])
|
||||
end
|
||||
end
|
151
installer/pleroma/installer_web/controllers/setup_controller.ex
Normal file
151
installer/pleroma/installer_web/controllers/setup_controller.ex
Normal file
@ -0,0 +1,151 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.InstallerWeb.SetupController do
|
||||
use Pleroma.InstallerWeb, :controller
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.InstallerWeb.Forms.ConfigForm
|
||||
alias Pleroma.InstallerWeb.Forms.CredentialsForm
|
||||
|
||||
plug(:authenticate)
|
||||
|
||||
def index(conn, _) do
|
||||
env = Config.get(:env)
|
||||
|
||||
render(conn, "index.html",
|
||||
credentials:
|
||||
CredentialsForm.changeset(%{
|
||||
username: "pleroma",
|
||||
password: "",
|
||||
database: "pleroma_#{env}",
|
||||
hostname: "localhost"
|
||||
}),
|
||||
error: nil
|
||||
)
|
||||
end
|
||||
|
||||
def save_credentials(conn, params) do
|
||||
changeset = CredentialsForm.changeset(params["credentials_form"], generate_password: true)
|
||||
|
||||
case CredentialsForm.save_credentials(changeset) do
|
||||
:ok ->
|
||||
redirect(conn, to: Routes.setup_path(conn, :migrations))
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
render(conn, "index.html",
|
||||
credentials: changeset,
|
||||
error: nil
|
||||
)
|
||||
|
||||
{:error, %DBConnection.ConnectionError{}, _} ->
|
||||
render(conn, "index.html",
|
||||
credentials: changeset,
|
||||
error:
|
||||
"Pleroma can't connect to the database with these credentials. Please check them and try one more time."
|
||||
)
|
||||
|
||||
{:error, :create_repo, psql_path} ->
|
||||
render(conn, "run_psql.html", psql_path: psql_path, error: nil)
|
||||
|
||||
{:error, error, _} ->
|
||||
render(conn, "index.html",
|
||||
credentials: changeset,
|
||||
error: inspect(error)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def recheck(conn, _) do
|
||||
case CredentialsForm.check_database_and_write_config() do
|
||||
:ok ->
|
||||
redirect(conn, to: Routes.setup_path(conn, :migrations))
|
||||
|
||||
{:error, %DBConnection.ConnectionError{}, _} ->
|
||||
render(conn, "run_psql.html",
|
||||
error:
|
||||
"Pleroma can't connect to the database with these credentials. Please check them and try one more time."
|
||||
)
|
||||
|
||||
{:error, :extensions_not_installed, _} ->
|
||||
render(conn, "run_psql.html", error: "Some required extensions were not found.")
|
||||
|
||||
{:error, error, _} ->
|
||||
render(conn, "run_psql.html", error: inspect(error))
|
||||
end
|
||||
end
|
||||
|
||||
def migrations(conn, _) do
|
||||
render(conn, "migrations.html")
|
||||
end
|
||||
|
||||
def run_migrations(conn, _) do
|
||||
response =
|
||||
case CredentialsForm.migrations() do
|
||||
:ok -> "ok"
|
||||
_ -> "Error occuried while migrations were run."
|
||||
end
|
||||
|
||||
json(conn, response)
|
||||
end
|
||||
|
||||
def config(conn, _) do
|
||||
render(conn, "config.html",
|
||||
config:
|
||||
ConfigForm.changeset(%{
|
||||
instance_static_dir: "instance/static",
|
||||
endpoint_url_port: 443,
|
||||
endpoint_http_ip: "127.0.0.1",
|
||||
endpoint_http_port: 4000,
|
||||
local_uploads_dir: "uploads"
|
||||
}),
|
||||
error: nil
|
||||
)
|
||||
end
|
||||
|
||||
def save_config(conn, params) do
|
||||
changeset = ConfigForm.changeset(params["config_form"])
|
||||
|
||||
case ConfigForm.save(changeset) do
|
||||
:ok ->
|
||||
Config.delete(:installer_token)
|
||||
|
||||
if Config.get(:env) != :test do
|
||||
if endpoint = Process.whereis(Pleroma.Web.Endpoint) do
|
||||
Supervisor.stop(endpoint)
|
||||
end
|
||||
|
||||
Pleroma.Application.stop_installer_and_start_pleroma()
|
||||
end
|
||||
|
||||
redirect(conn, external: Pleroma.Web.Endpoint.url())
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
render(conn, "config.html", config: changeset, error: "Some values have incorrect values.")
|
||||
|
||||
{:error, :config_file_not_found} ->
|
||||
render(conn, "config.html", config: changeset, error: "Something went wrong.")
|
||||
|
||||
{:error, error} ->
|
||||
render(conn, "config.html", config: changeset, error: inspect(error))
|
||||
end
|
||||
end
|
||||
|
||||
defp authenticate(conn, _) do
|
||||
token = Config.get(:installer_token)
|
||||
|
||||
cond do
|
||||
token && get_session(conn, :token) == token ->
|
||||
conn
|
||||
|
||||
token && conn.query_params["token"] == token ->
|
||||
put_session(conn, :token, token)
|
||||
|
||||
true ->
|
||||
conn
|
||||
|> text("Token is invalid")
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
end
|
34
installer/pleroma/installer_web/endpoint.ex
Normal file
34
installer/pleroma/installer_web/endpoint.ex
Normal file
@ -0,0 +1,34 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.InstallerWeb.Endpoint do
|
||||
use Phoenix.Endpoint, otp_app: :pleroma
|
||||
|
||||
@session_options [
|
||||
store: :cookie,
|
||||
key: "_pleroma_installer_key",
|
||||
signing_salt: "4aGH1qnr"
|
||||
]
|
||||
|
||||
plug(Plug.Static,
|
||||
at: "/",
|
||||
from: {:pleroma, "priv/static/installer"},
|
||||
gzip: false,
|
||||
only: ~w(css images js favicon.ico)
|
||||
)
|
||||
|
||||
if code_reloading? do
|
||||
plug(Phoenix.CodeReloader)
|
||||
end
|
||||
|
||||
plug(Plug.Parsers,
|
||||
parsers: [:urlencoded],
|
||||
pass: ["*/*"],
|
||||
json_decoder: Phoenix.json_library()
|
||||
)
|
||||
|
||||
plug(Plug.MethodOverride)
|
||||
plug(Plug.Session, @session_options)
|
||||
plug(Pleroma.InstallerWeb.Router)
|
||||
end
|
51
installer/pleroma/installer_web/error_helpers.ex
Normal file
51
installer/pleroma/installer_web/error_helpers.ex
Normal file
@ -0,0 +1,51 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.InstallerWeb.ErrorHelpers do
|
||||
@moduledoc """
|
||||
Conveniences for translating and building error messages.
|
||||
"""
|
||||
|
||||
use Phoenix.HTML
|
||||
|
||||
@doc """
|
||||
Generates tag for inlined form input errors.
|
||||
"""
|
||||
def error_tag(form, field) do
|
||||
Enum.map(Keyword.get_values(form.errors, field), fn error ->
|
||||
content_tag(:span, translate_error(error),
|
||||
class: "help-block",
|
||||
data: [phx_error_for: input_id(form, field)]
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Translates an error message using gettext.
|
||||
"""
|
||||
def translate_error({msg, opts}) do
|
||||
# When using gettext, we typically pass the strings we want
|
||||
# to translate as a static argument:
|
||||
#
|
||||
# # Translate "is invalid" in the "errors" domain
|
||||
# dgettext("errors", "is invalid")
|
||||
#
|
||||
# # Translate the number of files with plural rules
|
||||
# dngettext("errors", "1 file", "%{count} files", count)
|
||||
#
|
||||
# Because the error messages we show in our forms and APIs
|
||||
# are defined inside Ecto, we need to translate them dynamically.
|
||||
# This requires us to call the Gettext module passing our gettext
|
||||
# backend as first argument.
|
||||
#
|
||||
# Note we use the "errors" domain, which means translations
|
||||
# should be written to the errors.po file. The :count option is
|
||||
# set by Ecto and indicates we should also apply plural rules.
|
||||
if count = opts[:count] do
|
||||
Gettext.dngettext(Pleroma.Web.Gettext, "errors", msg, msg, count, opts)
|
||||
else
|
||||
Gettext.dgettext(Pleroma.Web.Gettext, "errors", msg, opts)
|
||||
end
|
||||
end
|
||||
end
|
285
installer/pleroma/installer_web/forms/config_form.ex
Normal file
285
installer/pleroma/installer_web/forms/config_form.ex
Normal file
@ -0,0 +1,285 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.InstallerWeb.Forms.ConfigForm do
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.User
|
||||
|
||||
@to_file [
|
||||
:endpoint_url_scheme,
|
||||
:endpoint_url_host,
|
||||
:endpoint_url_port,
|
||||
:endpoint_http_ip,
|
||||
:endpoint_http_port,
|
||||
:endpoint_secret_key_base,
|
||||
:endpoint_signing_salt,
|
||||
:joken_default_signer,
|
||||
:configurable_from_database
|
||||
]
|
||||
|
||||
@primary_key false
|
||||
@repo Config.get([:installer, :repo], Pleroma.Installer.InstallerRepo)
|
||||
@admin_user_keys [:admin_email, :admin_nickname, :admin_password]
|
||||
|
||||
embedded_schema do
|
||||
field(:instance_name)
|
||||
field(:instance_email)
|
||||
field(:instance_notify_email)
|
||||
field(:instance_static_dir, :string)
|
||||
|
||||
field(:endpoint_url)
|
||||
field(:endpoint_url_host)
|
||||
field(:endpoint_url_port, :integer)
|
||||
field(:endpoint_url_scheme, :string)
|
||||
field(:endpoint_http_ip)
|
||||
field(:endpoint_http_port, :integer)
|
||||
field(:endpoint_secret_key_base)
|
||||
field(:endpoint_signing_salt)
|
||||
|
||||
field(:local_uploads_dir)
|
||||
|
||||
field(:joken_default_signer)
|
||||
|
||||
field(:web_push_encryption_public_key)
|
||||
field(:web_push_encryption_private_key)
|
||||
|
||||
field(:configurable_from_database, :boolean, default: true)
|
||||
field(:indexable, :boolean, default: true)
|
||||
|
||||
field(:create_admin_user, :boolean, default: true)
|
||||
field(:admin_nickname)
|
||||
field(:admin_email)
|
||||
field(:admin_password)
|
||||
end
|
||||
|
||||
@spec changeset(map()) :: Ecto.Changeset.t()
|
||||
def changeset(attrs \\ %{}) do
|
||||
keys = [
|
||||
:instance_name,
|
||||
:instance_email,
|
||||
:instance_notify_email,
|
||||
:instance_static_dir,
|
||||
:endpoint_url,
|
||||
:endpoint_http_ip,
|
||||
:endpoint_http_port,
|
||||
:local_uploads_dir,
|
||||
:configurable_from_database,
|
||||
:indexable,
|
||||
:create_admin_user
|
||||
]
|
||||
|
||||
%__MODULE__{}
|
||||
|> cast(
|
||||
attrs,
|
||||
keys ++ @admin_user_keys
|
||||
)
|
||||
|> validate_change(:endpoint_url, fn :endpoint_url, url ->
|
||||
case URI.parse(url) do
|
||||
%{scheme: nil} -> [endpoint_url: "url must have scheme"]
|
||||
%{host: nil} -> [endpoint_url: "url bad format"]
|
||||
_ -> []
|
||||
end
|
||||
end)
|
||||
|> set_url_fields()
|
||||
|> add_endpoint_secret()
|
||||
|> add_endpoint_signing_salt()
|
||||
|> add_joken_default_signer()
|
||||
|> add_web_push_keys()
|
||||
|> validate_required(keys)
|
||||
|> validate_format(:instance_email, User.email_regex())
|
||||
|> validate_format(:instance_notify_email, User.email_regex())
|
||||
|> maybe_validate_admin_user_fields()
|
||||
end
|
||||
|
||||
defp set_url_fields(%{changes: %{endpoint_url: url}} = changeset) do
|
||||
uri = URI.parse(url)
|
||||
|
||||
change(changeset,
|
||||
endpoint_url_host: uri.host,
|
||||
endpoint_url_port: uri.port,
|
||||
endpoint_url_scheme: uri.scheme
|
||||
)
|
||||
end
|
||||
|
||||
defp set_url_fields(changeset), do: changeset
|
||||
|
||||
defp add_endpoint_secret(changeset) do
|
||||
change(changeset, endpoint_secret_key_base: crypt(64))
|
||||
end
|
||||
|
||||
defp add_endpoint_signing_salt(changeset) do
|
||||
change(changeset, endpoint_signing_salt: crypt(8))
|
||||
end
|
||||
|
||||
defp add_joken_default_signer(changeset) do
|
||||
change(changeset, joken_default_signer: crypt(64))
|
||||
end
|
||||
|
||||
defp crypt(bytes) do
|
||||
bytes
|
||||
|> :crypto.strong_rand_bytes()
|
||||
|> Base.encode64()
|
||||
|> binary_part(0, bytes)
|
||||
end
|
||||
|
||||
defp add_web_push_keys(changeset) do
|
||||
{web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1)
|
||||
|
||||
change(changeset,
|
||||
web_push_encryption_public_key: Base.url_encode64(web_push_public_key, padding: false),
|
||||
web_push_encryption_private_key: Base.url_encode64(web_push_private_key, padding: false)
|
||||
)
|
||||
end
|
||||
|
||||
defp maybe_validate_admin_user_fields(%{changes: %{create_admin_user: false}} = changeset) do
|
||||
changeset
|
||||
end
|
||||
|
||||
defp maybe_validate_admin_user_fields(changeset) do
|
||||
changeset
|
||||
|> validate_required(@admin_user_keys)
|
||||
|> validate_format(:admin_email, User.email_regex())
|
||||
end
|
||||
|
||||
@spec save(Ecto.Changeset.t()) ::
|
||||
:ok
|
||||
| {:error, Ecto.Changeset.t()}
|
||||
| {:error, :config_file_not_found}
|
||||
| {:error, :file.posix()}
|
||||
def save(changeset) do
|
||||
with {:ok, struct} <- apply_action(changeset, :insert) do
|
||||
# we expect that config file was already created and contains database credentials,
|
||||
# so if file doesn't exist we return error
|
||||
config_path = Pleroma.Application.config_path()
|
||||
|
||||
if File.exists?(config_path) do
|
||||
config =
|
||||
struct
|
||||
|> Map.from_struct()
|
||||
|> Map.to_list()
|
||||
|
||||
with :ok <- do_save(config, config_path) do
|
||||
if config[:create_admin_user] do
|
||||
# in tests is always started
|
||||
if !Process.whereis(Pleroma.Web.Endpoint) do
|
||||
{:ok, _} = Pleroma.Web.Endpoint.start_link()
|
||||
end
|
||||
|
||||
params = %{
|
||||
nickname: config[:admin_nickname],
|
||||
email: config[:admin_email],
|
||||
password: config[:admin_password],
|
||||
password_confirmation: config[:admin_password],
|
||||
name: config[:admin_nickname],
|
||||
bio: ""
|
||||
}
|
||||
|
||||
changeset = User.register_changeset(%User{}, params, need_confirmation: false)
|
||||
|
||||
with {:ok, user} <- User.register(changeset) do
|
||||
{:ok, _user} = User.admin_api_update(user, %{is_admin: true})
|
||||
end
|
||||
end
|
||||
|
||||
generate_robots_txt(config)
|
||||
end
|
||||
else
|
||||
{:error, :config_file_not_found}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp do_save(config, config_path) do
|
||||
config
|
||||
|> save_to_file(config_path)
|
||||
|> maybe_save_to_database()
|
||||
end
|
||||
|
||||
defp save_to_file(config, config_path) do
|
||||
{keys, template} =
|
||||
if config[:configurable_from_database] do
|
||||
{@to_file, "installer/templates/config_part.eex"}
|
||||
else
|
||||
keys =
|
||||
[
|
||||
:local_uploads_dir,
|
||||
:web_push_encryption_public_key,
|
||||
:web_push_encryption_private_key,
|
||||
:instance_name,
|
||||
:instance_email,
|
||||
:instance_notify_email,
|
||||
:instance_static_dir
|
||||
] ++ @to_file
|
||||
|
||||
{keys, "installer/templates/config_full.eex"}
|
||||
end
|
||||
|
||||
assigns = Keyword.take(config, keys)
|
||||
|
||||
content = EEx.eval_file(template, assigns)
|
||||
|
||||
file_mod = Config.get([:installer, :file], Pleroma.Installer.File)
|
||||
|
||||
with :ok <- file_mod.write(config_path, ["\n", content], [:append]) do
|
||||
config
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_save_to_database(config) when is_list(config) do
|
||||
if config[:configurable_from_database] do
|
||||
web_push = [
|
||||
subject: "mailto:" <> config[:instance_email],
|
||||
public_key: config[:web_push_encryption_public_key],
|
||||
private_key: config[:web_push_encryption_private_key]
|
||||
]
|
||||
|
||||
changes = [
|
||||
%{
|
||||
group: :pleroma,
|
||||
key: :instance,
|
||||
value:
|
||||
Keyword.take(
|
||||
config,
|
||||
[:instance_name, :instance_email, :instance_notify_email, :instance_static_dir]
|
||||
)
|
||||
},
|
||||
%{
|
||||
group: :web_push_encryption,
|
||||
key: :vapid_details,
|
||||
value: web_push
|
||||
},
|
||||
%{
|
||||
group: :pleroma,
|
||||
key: Pleroma.Uploaders.Local,
|
||||
value: [uploads: config[:local_uploads_dir]]
|
||||
}
|
||||
]
|
||||
|
||||
config = Config.get(:credentials)
|
||||
|
||||
with {:ok, _} <- @repo.start_repo(config),
|
||||
{:ok, _} <- Config.Versioning.new_version(changes) do
|
||||
Config.delete(:credentials)
|
||||
end
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_save_to_database(result), do: result
|
||||
|
||||
defp generate_robots_txt(config) do
|
||||
templates_dir = Application.app_dir(:pleroma, "priv") <> "/templates"
|
||||
|
||||
Mix.Tasks.Pleroma.Instance.write_robots_txt(
|
||||
config[:instance_static_dir],
|
||||
config[:indexable],
|
||||
templates_dir
|
||||
)
|
||||
end
|
||||
end
|
148
installer/pleroma/installer_web/forms/credentials_form.ex
Normal file
148
installer/pleroma/installer_web/forms/credentials_form.ex
Normal file
@ -0,0 +1,148 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.InstallerWeb.Forms.CredentialsForm do
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
|
||||
require Logger
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Repo
|
||||
|
||||
@repo Config.get([:installer, :repo], Pleroma.Installer.InstallerRepo)
|
||||
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:username, :string)
|
||||
field(:password, :string, default: "")
|
||||
field(:database, :string)
|
||||
field(:hostname, :string)
|
||||
field(:pool_size, :integer, default: 2)
|
||||
field(:rum_enabled, :boolean, default: false)
|
||||
end
|
||||
|
||||
@spec changeset(map()) :: Ecto.Changeset.t()
|
||||
def changeset(attrs \\ %{}, opts \\ []) do
|
||||
%__MODULE__{}
|
||||
|> cast(attrs, [:username, :password, :database, :hostname, :rum_enabled])
|
||||
|> maybe_add_password(opts)
|
||||
|> validate_required([:username, :database, :hostname, :rum_enabled, :password])
|
||||
end
|
||||
|
||||
defp maybe_add_password(%{changes: %{password: _}} = changeset, _), do: changeset
|
||||
|
||||
defp maybe_add_password(changeset, opts) do
|
||||
if opts[:generate_password] do
|
||||
generated = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
|
||||
change(changeset, password: generated)
|
||||
else
|
||||
changeset
|
||||
end
|
||||
end
|
||||
|
||||
@spec save_credentials(Ecto.Changeset.t()) ::
|
||||
:ok
|
||||
| {:error, :psql_commands_execution, Path.t()}
|
||||
| {:error, Ecto.Changeset.t()}
|
||||
| {:error, term(), keyword()}
|
||||
def save_credentials(changeset) do
|
||||
with {:ok, struct} <- apply_action(changeset, :insert) do
|
||||
struct
|
||||
|> Map.from_struct()
|
||||
|> Keyword.new()
|
||||
|> check_database()
|
||||
|> maybe_create_database()
|
||||
|> write_config_file()
|
||||
end
|
||||
end
|
||||
|
||||
@spec check_database_and_write_config() :: :ok | {:error, term(), keyword()}
|
||||
def check_database_and_write_config do
|
||||
:credentials
|
||||
|> Config.get()
|
||||
|> check_database()
|
||||
|> write_config_file()
|
||||
end
|
||||
|
||||
defp check_database(credentials) when is_list(credentials) do
|
||||
case @repo.check_database(credentials) do
|
||||
:ok -> credentials
|
||||
error -> Tuple.append(error, credentials)
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_create_database(credentials) when is_list(credentials), do: credentials
|
||||
|
||||
defp maybe_create_database({:error, _, credentials}) do
|
||||
# we stop started repo in `check_database`
|
||||
@repo.stop()
|
||||
|
||||
case @repo.create_database(credentials) do
|
||||
:ok ->
|
||||
@repo.stop()
|
||||
check_database(credentials)
|
||||
|
||||
{:error, :create_repo} ->
|
||||
# something went wrong with repo creation,
|
||||
# we save psql file and let the user to run it manually
|
||||
Config.put(:credentials, credentials)
|
||||
psql_path = "/tmp/setup_db.psql"
|
||||
|
||||
Logger.warn("Writing the postgres script to #{psql_path}.")
|
||||
|
||||
psql =
|
||||
EEx.eval_file(
|
||||
"installer/templates/sample_psql.eex",
|
||||
credentials
|
||||
)
|
||||
|
||||
with :ok <- File.write(psql_path, psql) do
|
||||
{:error, :create_repo, psql_path}
|
||||
end
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
defp write_config_file(credentials) when is_list(credentials) do
|
||||
config_path = Pleroma.Application.config_path()
|
||||
|
||||
config = EEx.eval_file("installer/templates/credentials.eex", credentials)
|
||||
|
||||
case File.write(config_path, config) do
|
||||
:ok ->
|
||||
updated_config = Keyword.merge(Repo.config(), credentials)
|
||||
|
||||
Config.put(Repo, updated_config)
|
||||
Config.put([:database, :rum_enabled], credentials[:rum_enabled])
|
||||
Config.put(:credentials, credentials)
|
||||
|
||||
@repo.stop()
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
defp write_config_file(error), do: error
|
||||
|
||||
@spec migrations() :: :ok | {:error, :migrations_error}
|
||||
def migrations do
|
||||
path = Ecto.Migrator.migrations_path(Repo)
|
||||
|
||||
paths =
|
||||
if Config.get([:database, :rum_enabled]) do
|
||||
[path, "priv/repo/optional_migrations/rum_indexing/"]
|
||||
else
|
||||
path
|
||||
end
|
||||
|
||||
Config.get(:credentials)
|
||||
|> @repo.run_migrations(paths)
|
||||
end
|
||||
end
|
31
installer/pleroma/installer_web/router.ex
Normal file
31
installer/pleroma/installer_web/router.ex
Normal file
@ -0,0 +1,31 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.InstallerWeb.Router do
|
||||
use Pleroma.InstallerWeb, :router
|
||||
|
||||
pipeline :browser do
|
||||
plug(:accepts, ["html"])
|
||||
plug(:fetch_session)
|
||||
plug(:fetch_flash)
|
||||
plug(:protect_from_forgery)
|
||||
plug(:put_secure_browser_headers)
|
||||
end
|
||||
|
||||
scope "/", Pleroma.InstallerWeb do
|
||||
pipe_through(:browser)
|
||||
|
||||
get("/", SetupController, :index)
|
||||
|
||||
post("/credentials", SetupController, :save_credentials)
|
||||
|
||||
get("/recheck", SetupController, :recheck)
|
||||
|
||||
get("/migrations", SetupController, :migrations)
|
||||
get("/run_migrations", SetupController, :run_migrations)
|
||||
|
||||
get("/config", SetupController, :config)
|
||||
post("/config", SetupController, :save_config)
|
||||
end
|
||||
end
|
@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Pleroma Installer</title>
|
||||
<link rel=icon type=image/png href=<%= Routes.static_path(@conn, "/images/favicon.png") %>>
|
||||
<link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="container" style="margin-top: 3rem;">
|
||||
<div style="float: right; margin: -20px -20px 0 0;"><img width="80" src="<%= Routes.static_path(@conn, "/images/logo.svg") %>" alt="Pleroma Logo"/></div>
|
||||
<h1>Pleroma Installer</h1>
|
||||
</div>
|
||||
</header>
|
||||
<main role="main" class="container">
|
||||
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
|
||||
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
|
||||
<%= @inner_content %>
|
||||
</main>
|
||||
<script type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
|
||||
</body>
|
||||
</html>
|
124
installer/pleroma/installer_web/templates/setup/config.html.eex
Normal file
124
installer/pleroma/installer_web/templates/setup/config.html.eex
Normal file
@ -0,0 +1,124 @@
|
||||
<section class="row">
|
||||
<article class="column">
|
||||
<%= if @error do %>
|
||||
<p style="color: red;"> Error occuried: <%= @error %></p>
|
||||
<a class="button" href="<%= Routes.setup_path(@conn, :index) %>">Try one more time</a>
|
||||
<% end %>
|
||||
<%= form_for @config, Routes.setup_path(@conn, :save_config), fn f -> %>
|
||||
<div>
|
||||
<h4>What is the name of your instance?</h4>
|
||||
<%= text_input f, :instance_name, placeholder: "Pleroma/Soykaf"%>
|
||||
<%= error_tag f, :instance_name %>
|
||||
</div>
|
||||
<div>
|
||||
<h4>What directory should custom public files be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)?</h4>
|
||||
<%= text_input f, :instance_static_dir, placeholder: "instance/static" %>
|
||||
<%= error_tag f, :instance_static_dir %>
|
||||
</div>
|
||||
<div>
|
||||
<h4>What directory should media uploads go in (when using the local uploader)?</h4>
|
||||
<%= text_input f, :local_uploads_dir, placeholder: "uploads" %>
|
||||
<%= error_tag f, :local_uploads_dir %>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<label>URL</label>
|
||||
<%= text_input f, :endpoint_url, placeholder: "https://pleroma.com" %>
|
||||
<%= error_tag f, :endpoint_url %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<div>
|
||||
<h4>Network Settings</h4>
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<label>IP</label>
|
||||
<%= text_input f, :endpoint_http_ip, placeholder: "127.0.0.1" %>
|
||||
<%= error_tag f, :endpoint_http_ip %>
|
||||
</div>
|
||||
<div class="column">
|
||||
<label>Port</label>
|
||||
<%= text_input f, :endpoint_http_port, placeholder: 4000 %>
|
||||
<%= error_tag f, :endpoint_http_port %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4>Email</h4>
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<label>Admin Email</label>
|
||||
<%= email_input f, :instance_email, placeholder: "user@example.com" %>
|
||||
<%= error_tag f, :instance_email %>
|
||||
</div>
|
||||
<div class="column">
|
||||
<label>Email For Sending Notifications</label>
|
||||
<%= email_input f, :instance_notify_email, placeholder: "user@example.com" %>
|
||||
<%= error_tag f, :instance_notify_email %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<div>
|
||||
<h4>Do you want to deny Search Engine bots from crawling the site?</h4>
|
||||
<%= radio_button f, :indexable, true %>
|
||||
<%= label f, :indexable, "No", class: "label-inline" %>
|
||||
<%= radio_button f, :indexable, false, style: "margin-left: 1.5rem;" %>
|
||||
<%= label f, :indexable, "Yes", class: "label-inline" %>
|
||||
</div>
|
||||
<hr>
|
||||
<div>
|
||||
<h4>Do you want to store the configuration in the database (allows controlling it from admin-fe)?</h4>
|
||||
<%= radio_button f, :configurable_from_database, true %>
|
||||
<%= label f, :configurable_from_database, "Yes", class: "label-inline" %>
|
||||
<%= radio_button f, :configurable_from_database, false, style: "margin-left: 1.5rem;" %>
|
||||
<%= label f, :configurable_from_database, "No", class: "label-inline" %>
|
||||
</div>
|
||||
<hr>
|
||||
<div>
|
||||
<h4>Admin user</h4>
|
||||
<h4>Fill in the fields below, if you want to create admin user.</h4>
|
||||
<%= radio_button f, :create_admin_user, true %>
|
||||
<%= label f, :create_admin_user, "Yes", class: "label-inline" %>
|
||||
<%= radio_button f, :create_admin_user, false, style: "margin-left: 1.5rem;" %>
|
||||
<%= label f, :create_admin_user, "No", class: "label-inline" %>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<label>Nickname</label>
|
||||
<%= text_input f, :admin_nickname, placeholder: "Lain" %>
|
||||
<%= error_tag f, :admin_nickname %>
|
||||
</div>
|
||||
<div class="column">
|
||||
<label>Email</label>
|
||||
<%= email_input f, :admin_email, placeholder: "user@example.com" %>
|
||||
<%= error_tag f, :admin_email %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<label>Password</label>
|
||||
<%= password_input f, :admin_password %>
|
||||
<%= error_tag f, :admin_password %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<%= submit "Save Config" %>
|
||||
<% end %>
|
||||
</article>
|
||||
</section>
|
@ -0,0 +1,48 @@
|
||||
<section class="row">
|
||||
<article class="column">
|
||||
<%= if @error do %>
|
||||
<p style="color: red;"> Error occuried: <%= @error %></p>
|
||||
<% end %>
|
||||
<%= form_for @credentials, Routes.setup_path(@conn, :save_credentials), fn f -> %>
|
||||
<div>
|
||||
<h3>Database settings</h3>
|
||||
<p>If you have already configured PostgreSQL, please fill in your credentials. If not, please fill in preferred credentials, so the installer can prepare the database.</p>
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<%= label f, :hostname %>
|
||||
<%= text_input f, :hostname %>
|
||||
<%= error_tag f, :hostname %>
|
||||
</div>
|
||||
<div class="column">
|
||||
<%= label f, :database %>
|
||||
<%= text_input f, :database %>
|
||||
<%= error_tag f, :database %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<%= label f, :username %>
|
||||
<%= text_input f, :username %>
|
||||
<%= error_tag f, :username %>
|
||||
</div>
|
||||
<div class="column">
|
||||
<%= label f, :password, "Password (if empty will be autogenerated)" %>
|
||||
<%= text_input f, :password %>
|
||||
<%= error_tag f, :password %>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<h4>Would you like to use RUM indices?</h4>
|
||||
<%= radio_button f, :rum_enabled, true %>
|
||||
<%= label f, :rum_enabled, "Yes", class: "label-inline" %>
|
||||
<%= radio_button f, :rum_enabled, false, style: "margin-left: 1.5rem;" %>
|
||||
<%= label f, :rum_enabled, "No", class: "label-inline" %>
|
||||
<p>RUM extension must be <a href="https://github.com/postgrespro/rum#installation" target="_blank">installed</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<%= submit "Save credentials" %>
|
||||
<% end %>
|
||||
</article>
|
||||
</section>
|
@ -0,0 +1,8 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="column" id="migrations">
|
||||
<p>The database is almost ready. Migrations are running.</p>
|
||||
<p style="color: red; visibility: hidden;" id="error">Something went wrong. <a class="button" href="<%= Routes.setup_path(@conn, :index) %>">Start over</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,19 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<%= if @error do %>
|
||||
<p style="color: red;"><%= @error %></p>
|
||||
<p>Are you sure psql file was executed?</p>
|
||||
|
||||
<a class="button" href="<%= Routes.setup_path(@conn, :recheck) %>">Try one more time</a>
|
||||
<a class="button" href="<%= Routes.setup_path(@conn, :index) %>">Start over</a>
|
||||
<% else %>
|
||||
<p>Run following command to setup PostgreSQL:</p>
|
||||
<pre><code>
|
||||
sudo -Hu postgres psql -f <%= @psql_path %>
|
||||
</code></pre>
|
||||
<a class="button" href="<%= Routes.setup_path(@conn, :recheck) %>">Next</a>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
11
installer/pleroma/installer_web/views/error_view.ex
Normal file
11
installer/pleroma/installer_web/views/error_view.ex
Normal file
@ -0,0 +1,11 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.InstallerWeb.ErrorView do
|
||||
use Pleroma.InstallerWeb, :view
|
||||
|
||||
def template_not_found(template, _assigns) do
|
||||
Phoenix.Controller.status_message_from_template(template)
|
||||
end
|
||||
end
|
7
installer/pleroma/installer_web/views/layout_view.ex
Normal file
7
installer/pleroma/installer_web/views/layout_view.ex
Normal file
@ -0,0 +1,7 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.InstallerWeb.LayoutView do
|
||||
use Pleroma.InstallerWeb, :view
|
||||
end
|
7
installer/pleroma/installer_web/views/setup_view.ex
Normal file
7
installer/pleroma/installer_web/views/setup_view.ex
Normal file
@ -0,0 +1,7 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.InstallerWeb.SetupView do
|
||||
use Pleroma.InstallerWeb, :view
|
||||
end
|
53
installer/templates/config_full.eex
Normal file
53
installer/templates/config_full.eex
Normal file
@ -0,0 +1,53 @@
|
||||
config :pleroma, Pleroma.Web.Endpoint,
|
||||
url: [host: "<%= endpoint_url_host %>", scheme: "<%= endpoint_url_scheme %>", port: <%= endpoint_url_port %>],
|
||||
http: [ip: {<%= String.replace(endpoint_http_ip, ".", ", ") %>}, port: <%= endpoint_http_port %>],
|
||||
secret_key_base: "<%= endpoint_secret_key_base %>",
|
||||
signing_salt: "<%= endpoint_signing_salt %>"
|
||||
|
||||
config :pleroma, :instance,
|
||||
name: "<%= instance_name %>",
|
||||
email: "<%= instance_email %>",
|
||||
notify_email: "<%= instance_notify_email %>",
|
||||
limit: 5000,
|
||||
registrations_open: true,
|
||||
static_dir: "<%= instance_static_dir %>"
|
||||
|
||||
config :pleroma, :media_proxy,
|
||||
enabled: false,
|
||||
redirect_on_failure: true
|
||||
#base_url: "https://cache.pleroma.social"
|
||||
|
||||
# Configure web push notifications
|
||||
config :web_push_encryption, :vapid_details,
|
||||
subject: "mailto:<%= instance_email %>",
|
||||
public_key: "<%= web_push_encryption_public_key %>",
|
||||
private_key: "<%= web_push_encryption_private_key %>"
|
||||
|
||||
config :pleroma, Pleroma.Uploaders.Local, uploads: "<%= local_uploads_dir %>"
|
||||
|
||||
# Enable Strict-Transport-Security once SSL is working:
|
||||
# config :pleroma, :http_security,
|
||||
# sts: true
|
||||
|
||||
# Configure S3 support if desired.
|
||||
# The public S3 endpoint is different depending on region and provider,
|
||||
# consult your S3 provider's documentation for details on what to use.
|
||||
#
|
||||
# config :pleroma, Pleroma.Uploaders.S3,
|
||||
# bucket: "some-bucket",
|
||||
# public_endpoint: "https://s3.amazonaws.com"
|
||||
#
|
||||
# Configure S3 credentials:
|
||||
# config :ex_aws, :s3,
|
||||
# access_key_id: "xxxxxxxxxxxxx",
|
||||
# secret_access_key: "yyyyyyyyyyyy",
|
||||
# region: "us-east-1",
|
||||
# scheme: "https://"
|
||||
#
|
||||
# For using third-party S3 clones like wasabi, also do:
|
||||
# config :ex_aws, :s3,
|
||||
# host: "s3.wasabisys.com"
|
||||
|
||||
config :joken, default_signer: "<%= joken_default_signer %>"
|
||||
|
||||
config :pleroma, configurable_from_database: <%= configurable_from_database %>
|
9
installer/templates/config_part.eex
Normal file
9
installer/templates/config_part.eex
Normal file
@ -0,0 +1,9 @@
|
||||
config :pleroma, Pleroma.Web.Endpoint,
|
||||
url: [host: "<%= endpoint_url_host %>", scheme: "<%= endpoint_url_scheme %>", port: <%= endpoint_url_port %>],
|
||||
http: [ip: {<%= String.replace(endpoint_http_ip, ".", ", ") %>}, port: <%= endpoint_http_port %>],
|
||||
secret_key_base: "<%= endpoint_secret_key_base %>",
|
||||
signing_salt: "<%= endpoint_signing_salt %>"
|
||||
|
||||
config :pleroma, configurable_from_database: <%= configurable_from_database %>
|
||||
|
||||
config :joken, default_signer: "<%= joken_default_signer %>"
|
20
installer/templates/credentials.eex
Normal file
20
installer/templates/credentials.eex
Normal file
@ -0,0 +1,20 @@
|
||||
# Pleroma instance configuration
|
||||
|
||||
# NOTE: This file should not be committed to a repo or otherwise made public
|
||||
# without removing sensitive information.
|
||||
|
||||
<%= if Code.ensure_loaded?(Config) or not Code.ensure_loaded?(Mix.Config) do
|
||||
"import Config"
|
||||
else
|
||||
"use Mix.Config"
|
||||
end %>
|
||||
|
||||
config :pleroma, Pleroma.Repo,
|
||||
adapter: Ecto.Adapters.Postgres,
|
||||
username: "<%= username %>",
|
||||
password: "<%= password %>",
|
||||
database: "<%= database %>",
|
||||
hostname: "<%= hostname %>",
|
||||
pool_size: 10
|
||||
|
||||
config :pleroma, :database, rum_enabled: <%= rum_enabled %>
|
11
installer/templates/sample_psql.eex
Normal file
11
installer/templates/sample_psql.eex
Normal file
@ -0,0 +1,11 @@
|
||||
CREATE USER <%= username %> WITH ENCRYPTED PASSWORD '<%= password %>';
|
||||
CREATE DATABASE <%= database %> OWNER <%= username %>;
|
||||
\c <%= database %>;
|
||||
CREATE EXTENSION IF NOT EXISTS citext;
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
<%= if rum_enabled do
|
||||
"CREATE EXTENSION IF NOT EXISTS rum;"
|
||||
else
|
||||
""
|
||||
end %>
|
@ -4,7 +4,6 @@
|
||||
|
||||
defmodule Mix.Pleroma do
|
||||
@apps [
|
||||
:restarter,
|
||||
:ecto,
|
||||
:ecto_sql,
|
||||
:postgrex,
|
||||
@ -16,12 +15,15 @@ defmodule Mix.Pleroma do
|
||||
:fast_html,
|
||||
:oban
|
||||
]
|
||||
|
||||
@cachex_children ["object", "user", "scrubber", "web_resp"]
|
||||
|
||||
@doc "Common functions to be reused in mix tasks"
|
||||
@spec start_pleroma() :: {:ok, pid()}
|
||||
def start_pleroma do
|
||||
Pleroma.Config.Holder.save_default()
|
||||
Pleroma.Config.Oban.warn()
|
||||
Pleroma.Application.limiters_setup()
|
||||
Pleroma.Config.DeprecationWarnings.check_oban_config()
|
||||
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
|
||||
|
||||
unless System.get_env("DEBUG") do
|
||||
@ -47,37 +49,28 @@ defmodule Mix.Pleroma do
|
||||
plugins: []
|
||||
]
|
||||
|
||||
children =
|
||||
[
|
||||
Pleroma.Repo,
|
||||
Pleroma.Emoji,
|
||||
{Pleroma.Config.TransferTask, false},
|
||||
Pleroma.Web.Endpoint,
|
||||
{Oban, oban_config},
|
||||
{Majic.Pool,
|
||||
[name: Pleroma.MajicPool, pool_size: Pleroma.Config.get([:majic_pool, :size], 2)]}
|
||||
] ++
|
||||
http_children(adapter)
|
||||
children = [
|
||||
Pleroma.Application.ConfigDependentDeps,
|
||||
Pleroma.Repo,
|
||||
Pleroma.Emoji,
|
||||
Supervisor.child_spec({Task, &Pleroma.Application.Environment.load_from_db_and_update/0},
|
||||
id: :update_env
|
||||
),
|
||||
{Oban, oban_config},
|
||||
{Majic.Pool,
|
||||
[name: Pleroma.MajicPool, pool_size: Pleroma.Config.get([:majic_pool, :size], 2)]},
|
||||
Pleroma.Web.Endpoint
|
||||
]
|
||||
|
||||
cachex_children = Enum.map(@cachex_children, &Pleroma.Application.build_cachex(&1, []))
|
||||
children = [Pleroma.Application.StartUpDependencies.adapter_module() | children]
|
||||
|
||||
cachex_children =
|
||||
Enum.map(@cachex_children, &Pleroma.Application.StartUpDependencies.cachex_spec({&1, []}))
|
||||
|
||||
Supervisor.start_link(children ++ cachex_children,
|
||||
strategy: :one_for_one,
|
||||
name: Pleroma.Supervisor
|
||||
)
|
||||
|
||||
if Pleroma.Config.get(:env) not in [:test, :benchmark] do
|
||||
pleroma_rebooted?()
|
||||
end
|
||||
end
|
||||
|
||||
defp pleroma_rebooted? do
|
||||
if Restarter.Pleroma.rebooted?() do
|
||||
:ok
|
||||
else
|
||||
Process.sleep(10)
|
||||
pleroma_rebooted?()
|
||||
end
|
||||
end
|
||||
|
||||
def load_pleroma do
|
||||
@ -129,11 +122,4 @@ defmodule Mix.Pleroma do
|
||||
def escape_sh_path(path) do
|
||||
~S(') <> String.replace(path, ~S('), ~S(\')) <> ~S(')
|
||||
end
|
||||
|
||||
defp http_children(Tesla.Adapter.Gun) do
|
||||
Pleroma.Gun.ConnectionPool.children() ++
|
||||
[{Task, &Pleroma.HTTP.AdapterHelper.Gun.limiter_setup/0}]
|
||||
end
|
||||
|
||||
defp http_children(_), do: []
|
||||
end
|
||||
|
@ -14,10 +14,13 @@ defmodule Mix.Tasks.Pleroma.Config do
|
||||
@shortdoc "Manages the location of the config"
|
||||
@moduledoc File.read!("docs/administration/CLI_tasks/config.md")
|
||||
|
||||
def run(["migrate_to_db"]) do
|
||||
def run(["migrate_to_db" | options]) do
|
||||
check_configdb(fn ->
|
||||
start_pleroma()
|
||||
migrate_to_db()
|
||||
|
||||
{opts, _} = OptionParser.parse!(options, strict: [config: :string])
|
||||
|
||||
migrate_to_db(opts)
|
||||
end)
|
||||
end
|
||||
|
||||
@ -39,15 +42,13 @@ defmodule Mix.Tasks.Pleroma.Config do
|
||||
check_configdb(fn ->
|
||||
start_pleroma()
|
||||
|
||||
header = config_header()
|
||||
|
||||
settings =
|
||||
ConfigDB
|
||||
|> Repo.all()
|
||||
|> Enum.sort()
|
||||
|
||||
unless settings == [] do
|
||||
shell_info("#{header}")
|
||||
shell_info("#{Pleroma.Config.Loader.config_header()}")
|
||||
|
||||
Enum.each(settings, &dump(&1))
|
||||
else
|
||||
@ -73,9 +74,10 @@ defmodule Mix.Tasks.Pleroma.Config do
|
||||
check_configdb(fn ->
|
||||
start_pleroma()
|
||||
|
||||
group = maybe_atomize(group)
|
||||
|
||||
dump_group(group)
|
||||
group
|
||||
|> maybe_atomize()
|
||||
|> ConfigDB.get_all_by_group()
|
||||
|> Enum.each(&dump/1)
|
||||
end)
|
||||
end
|
||||
|
||||
@ -100,7 +102,7 @@ defmodule Mix.Tasks.Pleroma.Config do
|
||||
def run(["reset", "--force"]) do
|
||||
check_configdb(fn ->
|
||||
start_pleroma()
|
||||
truncatedb()
|
||||
Pleroma.Config.Versioning.reset()
|
||||
shell_info("The ConfigDB settings have been removed from the database.")
|
||||
end)
|
||||
end
|
||||
@ -119,7 +121,7 @@ defmodule Mix.Tasks.Pleroma.Config do
|
||||
shell_error("\nTHIS CANNOT BE UNDONE!")
|
||||
|
||||
if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
|
||||
truncatedb()
|
||||
Pleroma.Config.Versioning.reset()
|
||||
|
||||
shell_info("The ConfigDB settings have been removed from the database.")
|
||||
else
|
||||
@ -134,14 +136,12 @@ defmodule Mix.Tasks.Pleroma.Config do
|
||||
group = maybe_atomize(group)
|
||||
key = maybe_atomize(key)
|
||||
|
||||
with true <- key_exists?(group, key) do
|
||||
with %ConfigDB{} = config <- ConfigDB.get_by_group_and_key(group, key) do
|
||||
shell_info("The following settings will be removed from ConfigDB:\n")
|
||||
|
||||
group
|
||||
|> ConfigDB.get_by_group_and_key(key)
|
||||
|> dump()
|
||||
dump(config)
|
||||
|
||||
delete_key(group, key)
|
||||
Pleroma.Config.Versioning.new_version(%{group: config.group, key: config.key, delete: true})
|
||||
else
|
||||
_ ->
|
||||
shell_error("No settings in ConfigDB for #{inspect(group)}, #{inspect(key)}. Aborting.")
|
||||
@ -153,12 +153,22 @@ defmodule Mix.Tasks.Pleroma.Config do
|
||||
|
||||
group = maybe_atomize(group)
|
||||
|
||||
with true <- group_exists?(group) do
|
||||
configs = ConfigDB.get_all_by_group(group)
|
||||
|
||||
if configs != [] do
|
||||
shell_info("The following settings will be removed from ConfigDB:\n")
|
||||
dump_group(group)
|
||||
delete_group(group)
|
||||
|
||||
Enum.each(configs, fn config ->
|
||||
dump(config)
|
||||
|
||||
Pleroma.Config.Versioning.new_version(%{
|
||||
group: config.group,
|
||||
key: config.key,
|
||||
delete: true
|
||||
})
|
||||
end)
|
||||
else
|
||||
_ -> shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.")
|
||||
shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.")
|
||||
end
|
||||
end
|
||||
|
||||
@ -168,15 +178,17 @@ defmodule Mix.Tasks.Pleroma.Config do
|
||||
group = maybe_atomize(group)
|
||||
key = maybe_atomize(key)
|
||||
|
||||
with true <- key_exists?(group, key) do
|
||||
with %ConfigDB{} = config <- ConfigDB.get_by_group_and_key(group, key) do
|
||||
shell_info("The following settings will be removed from ConfigDB:\n")
|
||||
|
||||
group
|
||||
|> ConfigDB.get_by_group_and_key(key)
|
||||
|> dump()
|
||||
dump(config)
|
||||
|
||||
if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
|
||||
delete_key(group, key)
|
||||
Pleroma.Config.Versioning.new_version(%{
|
||||
group: config.group,
|
||||
key: config.key,
|
||||
delete: true
|
||||
})
|
||||
else
|
||||
shell_error("No changes made.")
|
||||
end
|
||||
@ -191,35 +203,67 @@ defmodule Mix.Tasks.Pleroma.Config do
|
||||
|
||||
group = maybe_atomize(group)
|
||||
|
||||
with true <- group_exists?(group) do
|
||||
configs = ConfigDB.get_all_by_group(group)
|
||||
|
||||
if configs != [] do
|
||||
shell_info("The following settings will be removed from ConfigDB:\n")
|
||||
dump_group(group)
|
||||
Enum.each(configs, &dump/1)
|
||||
|
||||
if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
|
||||
delete_group(group)
|
||||
Enum.each(configs, fn config ->
|
||||
Pleroma.Config.Versioning.new_version(%{
|
||||
group: config.group,
|
||||
key: config.key,
|
||||
delete: true
|
||||
})
|
||||
end)
|
||||
else
|
||||
shell_error("No changes made.")
|
||||
end
|
||||
else
|
||||
_ -> shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.")
|
||||
shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.")
|
||||
end
|
||||
end
|
||||
|
||||
@spec migrate_to_db(Path.t() | nil) :: any()
|
||||
def migrate_to_db(file_path \\ nil) do
|
||||
with :ok <- Pleroma.Config.DeprecationWarnings.warn() do
|
||||
config_file =
|
||||
if file_path do
|
||||
file_path
|
||||
else
|
||||
if Pleroma.Config.get(:release) do
|
||||
Pleroma.Config.get(:config_path)
|
||||
else
|
||||
"config/#{Pleroma.Config.get(:env)}.secret.exs"
|
||||
end
|
||||
end
|
||||
def run(["rollback" | options]) do
|
||||
check_configdb(fn ->
|
||||
start_pleroma()
|
||||
{opts, _} = OptionParser.parse!(options, strict: [steps: :integer], aliases: [s: :steps])
|
||||
|
||||
do_migrate_to_db(config_file)
|
||||
do_rollback(opts)
|
||||
end)
|
||||
end
|
||||
|
||||
defp do_rollback(opts) do
|
||||
steps = opts[:steps] || 1
|
||||
|
||||
case Pleroma.Config.Versioning.rollback(steps) do
|
||||
{:ok, _} ->
|
||||
shell_info("Success rollback")
|
||||
|
||||
{:error, :no_current_version} ->
|
||||
shell_error("No version to rollback")
|
||||
|
||||
{:error, :rollback_not_possible} ->
|
||||
shell_error("Rollback not possible. Incorrect steps value.")
|
||||
|
||||
{:error, _, _, _} ->
|
||||
shell_error("Problem with backup. Rollback not possible.")
|
||||
|
||||
error ->
|
||||
shell_error("error occuried: #{inspect(error)}")
|
||||
end
|
||||
end
|
||||
|
||||
defp migrate_to_db(opts) do
|
||||
with :ok <- Pleroma.Config.DeprecationWarnings.warn() do
|
||||
config_file = opts[:config] || Pleroma.Application.config_path()
|
||||
|
||||
if File.exists?(config_file) do
|
||||
do_migrate_to_db(config_file)
|
||||
else
|
||||
shell_info("To migrate settings, you must define custom settings in #{config_file}.")
|
||||
end
|
||||
else
|
||||
_ ->
|
||||
shell_error("Migration is not allowed until all deprecation warnings have been resolved.")
|
||||
@ -227,33 +271,9 @@ defmodule Mix.Tasks.Pleroma.Config do
|
||||
end
|
||||
|
||||
defp do_migrate_to_db(config_file) do
|
||||
if File.exists?(config_file) do
|
||||
shell_info("Migrating settings from file: #{Path.expand(config_file)}")
|
||||
truncatedb()
|
||||
|
||||
custom_config =
|
||||
config_file
|
||||
|> read_file()
|
||||
|> elem(0)
|
||||
|
||||
custom_config
|
||||
|> Keyword.keys()
|
||||
|> Enum.each(&create(&1, custom_config))
|
||||
else
|
||||
shell_info("To migrate settings, you must define custom settings in #{config_file}.")
|
||||
end
|
||||
end
|
||||
|
||||
defp create(group, settings) do
|
||||
group
|
||||
|> Pleroma.Config.Loader.filter_group(settings)
|
||||
|> Enum.each(fn {key, value} ->
|
||||
{:ok, _} = ConfigDB.update_or_create(%{group: group, key: key, value: value})
|
||||
|
||||
shell_info("Settings for key #{key} migrated.")
|
||||
end)
|
||||
|
||||
shell_info("Settings for group #{inspect(group)} migrated.")
|
||||
shell_info("Migrating settings from file: #{Path.expand(config_file)}")
|
||||
{:ok, _} = Pleroma.Config.Versioning.migrate(config_file)
|
||||
shell_info("Settings migrated.")
|
||||
end
|
||||
|
||||
defp migrate_from_db(opts) do
|
||||
@ -270,12 +290,42 @@ defmodule Mix.Tasks.Pleroma.Config do
|
||||
|> Path.join("#{env}.exported_from_db.secret.exs")
|
||||
|
||||
file = File.open!(config_path, [:write, :utf8])
|
||||
IO.write(file, Pleroma.Config.Loader.config_header())
|
||||
|
||||
IO.write(file, config_header())
|
||||
changes =
|
||||
ConfigDB
|
||||
|> Repo.all()
|
||||
|> Enum.reduce([], fn %{group: group} = config, acc ->
|
||||
group_str = inspect(group)
|
||||
value = inspect(config.value, limit: :infinity)
|
||||
|
||||
ConfigDB
|
||||
|> Repo.all()
|
||||
|> Enum.each(&write_and_delete(&1, file, opts[:delete]))
|
||||
msg =
|
||||
if group in ConfigDB.groups_without_keys() do
|
||||
IO.write(file, "config #{group_str}, #{value}\r\n\r\n")
|
||||
"config #{group_str} was deleted."
|
||||
else
|
||||
key_str = inspect(config.key)
|
||||
IO.write(file, "config #{group_str}, #{key_str}, #{value}\r\n\r\n")
|
||||
"config #{group_str}, #{key_str} was deleted."
|
||||
end
|
||||
|
||||
if opts[:delete] do
|
||||
shell_info(msg)
|
||||
|
||||
change =
|
||||
config
|
||||
|> Map.take([:group, :key])
|
||||
|> Map.put(:delete, true)
|
||||
|
||||
[change | acc]
|
||||
else
|
||||
acc
|
||||
end
|
||||
end)
|
||||
|
||||
if opts[:delete] and changes != [] do
|
||||
Pleroma.Config.Versioning.new_version(changes)
|
||||
end
|
||||
|
||||
:ok = File.close(file)
|
||||
System.cmd("mix", ["format", config_path])
|
||||
@ -285,38 +335,6 @@ defmodule Mix.Tasks.Pleroma.Config do
|
||||
)
|
||||
end
|
||||
|
||||
if Code.ensure_loaded?(Config.Reader) do
|
||||
defp config_header, do: "import Config\r\n\r\n"
|
||||
defp read_file(config_file), do: Config.Reader.read_imports!(config_file)
|
||||
else
|
||||
defp config_header, do: "use Mix.Config\r\n\r\n"
|
||||
defp read_file(config_file), do: Mix.Config.eval!(config_file)
|
||||
end
|
||||
|
||||
defp write_and_delete(config, file, delete?) do
|
||||
config
|
||||
|> write(file)
|
||||
|> delete(delete?)
|
||||
end
|
||||
|
||||
defp write(config, file) do
|
||||
value = inspect(config.value, limit: :infinity)
|
||||
|
||||
IO.write(file, "config #{inspect(config.group)}, #{inspect(config.key)}, #{value}\r\n\r\n")
|
||||
|
||||
config
|
||||
end
|
||||
|
||||
defp delete(config, true) do
|
||||
{:ok, _} = Repo.delete(config)
|
||||
|
||||
shell_info(
|
||||
"config #{inspect(config.group)}, #{inspect(config.key)} was deleted from the ConfigDB."
|
||||
)
|
||||
end
|
||||
|
||||
defp delete(_config, _), do: :ok
|
||||
|
||||
defp dump(%ConfigDB{} = config) do
|
||||
value = inspect(config.value, limit: :infinity)
|
||||
|
||||
@ -325,31 +343,12 @@ defmodule Mix.Tasks.Pleroma.Config do
|
||||
|
||||
defp dump(_), do: :noop
|
||||
|
||||
defp dump_group(group) when is_atom(group) do
|
||||
group
|
||||
|> ConfigDB.get_all_by_group()
|
||||
|> Enum.each(&dump/1)
|
||||
end
|
||||
|
||||
defp group_exists?(group) do
|
||||
group
|
||||
|> ConfigDB.get_all_by_group()
|
||||
|> Enum.any?()
|
||||
end
|
||||
|
||||
defp key_exists?(group, key) do
|
||||
group
|
||||
|> ConfigDB.get_by_group_and_key(key)
|
||||
|> is_nil
|
||||
|> Kernel.!()
|
||||
end
|
||||
|
||||
defp maybe_atomize(arg) when is_atom(arg), do: arg
|
||||
|
||||
defp maybe_atomize(":" <> arg), do: maybe_atomize(arg)
|
||||
|
||||
defp maybe_atomize(arg) when is_binary(arg) do
|
||||
if ConfigDB.module_name?(arg) do
|
||||
if Pleroma.Config.Converter.module_name?(arg) do
|
||||
String.to_existing_atom("Elixir." <> arg)
|
||||
else
|
||||
String.to_atom(arg)
|
||||
@ -366,23 +365,4 @@ defmodule Mix.Tasks.Pleroma.Config do
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp delete_key(group, key) do
|
||||
check_configdb(fn ->
|
||||
ConfigDB.delete(%{group: group, key: key})
|
||||
end)
|
||||
end
|
||||
|
||||
defp delete_group(group) do
|
||||
check_configdb(fn ->
|
||||
group
|
||||
|> ConfigDB.get_all_by_group()
|
||||
|> Enum.each(&ConfigDB.delete/1)
|
||||
end)
|
||||
end
|
||||
|
||||
defp truncatedb do
|
||||
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;")
|
||||
Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;")
|
||||
end
|
||||
end
|
||||
|
@ -32,7 +32,7 @@ defmodule Mix.Tasks.Pleroma.Docs do
|
||||
defp do_run(implementation) do
|
||||
start_pleroma()
|
||||
|
||||
with descriptions <- Pleroma.Config.Loader.read("config/description.exs"),
|
||||
with descriptions <- Pleroma.Config.Loader.read!("config/description.exs"),
|
||||
{:ok, file_path} <-
|
||||
Pleroma.Docs.Generator.process(
|
||||
implementation,
|
||||
|
@ -86,9 +86,9 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
||||
get_option(
|
||||
options,
|
||||
:indexable,
|
||||
"Do you want search engines to index your site? (y/n)",
|
||||
"y"
|
||||
) === "y"
|
||||
"Do you want to deny Search Engine bots from crawling the site? (y/n)",
|
||||
"n"
|
||||
) === "n"
|
||||
|
||||
db_configurable? =
|
||||
get_option(
|
||||
@ -275,7 +275,8 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
||||
end
|
||||
end
|
||||
|
||||
defp write_robots_txt(static_dir, indexable, template_dir) do
|
||||
@spec write_robots_txt(Path.t(), boolean(), Path.t()) :: :ok | no_return()
|
||||
def write_robots_txt(static_dir, indexable, template_dir) do
|
||||
robots_txt =
|
||||
EEx.eval_file(
|
||||
template_dir <> "/robots_txt.eex",
|
||||
@ -289,7 +290,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
||||
shell_info("Backing up existing robots.txt to #{robots_txt_path}.bak")
|
||||
end
|
||||
|
||||
File.write(robots_txt_path, robots_txt)
|
||||
:ok = File.write(robots_txt_path, robots_txt)
|
||||
shell_info("Writing #{robots_txt_path}.")
|
||||
end
|
||||
|
||||
|
@ -5,8 +5,6 @@
|
||||
defmodule Pleroma.Application do
|
||||
use Application
|
||||
|
||||
import Cachex.Spec
|
||||
|
||||
alias Pleroma.Config
|
||||
|
||||
require Logger
|
||||
@ -15,12 +13,17 @@ defmodule Pleroma.Application do
|
||||
@version Mix.Project.config()[:version]
|
||||
@repository Mix.Project.config()[:source_url]
|
||||
@mix_env Mix.env()
|
||||
@dynamic_supervisor Pleroma.Application.Supervisor
|
||||
|
||||
@type env() :: :test | :benchmark | :dev | :prod
|
||||
|
||||
def name, do: @name
|
||||
def version, do: @version
|
||||
def named_version, do: @name <> " " <> @version
|
||||
def repository, do: @repository
|
||||
def dynamic_supervisor, do: @dynamic_supervisor
|
||||
|
||||
@spec user_agent() :: String.t()
|
||||
def user_agent do
|
||||
if Process.whereis(Pleroma.Web.Endpoint) do
|
||||
case Config.get([:http, :user_agent], :default) do
|
||||
@ -37,9 +40,92 @@ defmodule Pleroma.Application do
|
||||
end
|
||||
end
|
||||
|
||||
# See http://elixir-lang.org/docs/stable/elixir/Application.html
|
||||
# for more information on OTP Applications
|
||||
@spec config_path() :: Path.t()
|
||||
def config_path do
|
||||
if Config.get(:release) do
|
||||
Config.get(:config_path)
|
||||
else
|
||||
Config.get(:config_path_in_test) || "config/#{@mix_env}.secret.exs"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks that config file exists and starts application, otherwise starts web UI for configuration.
|
||||
Under main supervisor is started DynamicSupervisor, which later starts pleroma startup dependencies.
|
||||
Pleroma start is splitted into three `phases`:
|
||||
- running prestart requirements (runtime compilation, warnings, deprecations, monitoring, etc.)
|
||||
- loading and updating environment (if database config is used and enabled)
|
||||
- starting dependencies
|
||||
"""
|
||||
@impl true
|
||||
def start(_type, _args) do
|
||||
children = [
|
||||
{DynamicSupervisor, strategy: :one_for_one, name: @dynamic_supervisor},
|
||||
{Pleroma.Application.ConfigDependentDeps, [dynamic_supervisor: @dynamic_supervisor]}
|
||||
]
|
||||
|
||||
{:ok, main_supervisor} =
|
||||
Supervisor.start_link(children, strategy: :one_for_one, name: Pleroma.Supervisor)
|
||||
|
||||
if @mix_env == :test or File.exists?(Pleroma.Application.config_path()) do
|
||||
:ok = start_pleroma()
|
||||
else
|
||||
DynamicSupervisor.start_child(
|
||||
@dynamic_supervisor,
|
||||
Pleroma.InstallerWeb.Endpoint
|
||||
)
|
||||
|
||||
token = Ecto.UUID.generate()
|
||||
|
||||
Pleroma.Config.put(:installer_token, token)
|
||||
|
||||
installer_port =
|
||||
Pleroma.InstallerWeb.Endpoint
|
||||
|> Pleroma.Config.get()
|
||||
|> get_in([:http, :port])
|
||||
|
||||
ip =
|
||||
with {:ok, ip} <- Pleroma.Helpers.ServerIPHelper.real_ip() do
|
||||
ip
|
||||
else
|
||||
_ -> "IP not found"
|
||||
end
|
||||
|
||||
Logger.warn("Access installer at http://#{ip}:#{installer_port}/?token=#{token}")
|
||||
end
|
||||
|
||||
{:ok, main_supervisor}
|
||||
end
|
||||
|
||||
defp start_pleroma do
|
||||
{:ok, _} = DynamicSupervisor.start_child(@dynamic_supervisor, Pleroma.Repo)
|
||||
run_prestart_requirements()
|
||||
|
||||
Pleroma.Application.Environment.load_from_db_and_update()
|
||||
|
||||
Pleroma.Application.StartUpDependencies.start_all(@mix_env)
|
||||
end
|
||||
|
||||
@spec stop_installer_and_start_pleroma() :: {:ok, pid()}
|
||||
def stop_installer_and_start_pleroma do
|
||||
Pleroma.Application.config_path()
|
||||
|> Pleroma.Application.Environment.update()
|
||||
|
||||
start_pleroma()
|
||||
|
||||
Task.start(fn ->
|
||||
Process.sleep(100)
|
||||
|
||||
installer_endpoint = Process.whereis(Pleroma.InstallerWeb.Endpoint)
|
||||
|
||||
DynamicSupervisor.terminate_child(
|
||||
@dynamic_supervisor,
|
||||
installer_endpoint
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
defp run_prestart_requirements do
|
||||
# Scrubbers are compiled at runtime and therefore will cause a conflict
|
||||
# every time the application is restarted, so we disable module
|
||||
# conflicts at runtime
|
||||
@ -47,72 +133,26 @@ defmodule Pleroma.Application do
|
||||
# Disable warnings_as_errors at runtime, it breaks Phoenix live reload
|
||||
# due to protocol consolidation warnings
|
||||
Code.compiler_options(warnings_as_errors: false)
|
||||
Pleroma.Telemetry.Logger.attach()
|
||||
Config.Holder.save_default()
|
||||
|
||||
# compilation in runtime
|
||||
Pleroma.HTML.compile_scrubbers()
|
||||
Pleroma.Config.Oban.warn()
|
||||
compile_custom_modules()
|
||||
Pleroma.Docs.JSON.compile()
|
||||
|
||||
# telemetry and prometheus
|
||||
Pleroma.Telemetry.Logger.attach()
|
||||
setup_instrumenters()
|
||||
|
||||
Config.Holder.save_default()
|
||||
|
||||
Config.DeprecationWarnings.warn()
|
||||
Pleroma.Web.Plugs.HTTPSecurityPlug.warn_if_disabled()
|
||||
Pleroma.ApplicationRequirements.verify!()
|
||||
setup_instrumenters()
|
||||
load_custom_modules()
|
||||
Pleroma.Docs.JSON.compile()
|
||||
|
||||
limiters_setup()
|
||||
|
||||
adapter = Application.get_env(:tesla, :adapter)
|
||||
|
||||
if adapter == Tesla.Adapter.Gun do
|
||||
if version = Pleroma.OTPVersion.version() do
|
||||
[major, minor] =
|
||||
version
|
||||
|> String.split(".")
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
|> Enum.take(2)
|
||||
|
||||
if (major == 22 and minor < 2) or major < 22 do
|
||||
raise "
|
||||
!!!OTP VERSION WARNING!!!
|
||||
You are using gun adapter with OTP version #{version}, which doesn't support correct handling of unordered certificates chains. Please update your Erlang/OTP to at least 22.2.
|
||||
"
|
||||
end
|
||||
else
|
||||
raise "
|
||||
!!!OTP VERSION WARNING!!!
|
||||
To support correct handling of unordered certificates chains - OTP version must be > 22.2.
|
||||
"
|
||||
end
|
||||
end
|
||||
|
||||
# Define workers and child supervisors to be supervised
|
||||
children =
|
||||
[
|
||||
Pleroma.Repo,
|
||||
Config.TransferTask,
|
||||
Pleroma.Emoji,
|
||||
Pleroma.Web.Plugs.RateLimiter.Supervisor
|
||||
] ++
|
||||
cachex_children() ++
|
||||
http_children(adapter, @mix_env) ++
|
||||
[
|
||||
Pleroma.Stats,
|
||||
Pleroma.JobQueueMonitor,
|
||||
{Majic.Pool, [name: Pleroma.MajicPool, pool_size: Config.get([:majic_pool, :size], 2)]},
|
||||
{Oban, Config.get(Oban)},
|
||||
Pleroma.Web.Endpoint
|
||||
] ++
|
||||
task_children(@mix_env) ++
|
||||
dont_run_in_test(@mix_env) ++
|
||||
chat_child(chat_enabled?()) ++
|
||||
[Pleroma.Gopher.Server]
|
||||
|
||||
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
|
||||
# for other strategies and supported options
|
||||
opts = [strategy: :one_for_one, name: Pleroma.Supervisor]
|
||||
result = Supervisor.start_link(children, opts)
|
||||
|
||||
set_postgres_server_version()
|
||||
|
||||
result
|
||||
Pleroma.Application.Requirements.verify!()
|
||||
end
|
||||
|
||||
defp set_postgres_server_version do
|
||||
@ -132,7 +172,7 @@ defmodule Pleroma.Application do
|
||||
:persistent_term.put({Pleroma.Repo, :postgres_version}, version)
|
||||
end
|
||||
|
||||
def load_custom_modules do
|
||||
defp compile_custom_modules do
|
||||
dir = Config.get([:modules, :runtime_dir])
|
||||
|
||||
if dir && File.exists?(dir) do
|
||||
@ -177,128 +217,6 @@ defmodule Pleroma.Application do
|
||||
PrometheusPhx.setup()
|
||||
end
|
||||
|
||||
defp cachex_children do
|
||||
[
|
||||
build_cachex("used_captcha", ttl_interval: seconds_valid_interval()),
|
||||
build_cachex("user", default_ttl: 25_000, ttl_interval: 1000, limit: 2500),
|
||||
build_cachex("object", default_ttl: 25_000, ttl_interval: 1000, limit: 2500),
|
||||
build_cachex("rich_media", default_ttl: :timer.minutes(120), limit: 5000),
|
||||
build_cachex("scrubber", limit: 2500),
|
||||
build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500),
|
||||
build_cachex("web_resp", limit: 2500),
|
||||
build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10),
|
||||
build_cachex("failed_proxy_url", limit: 2500),
|
||||
build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000),
|
||||
build_cachex("chat_message_id_idempotency_key",
|
||||
expiration: chat_message_id_idempotency_key_expiration(),
|
||||
limit: 500_000
|
||||
)
|
||||
]
|
||||
end
|
||||
|
||||
defp emoji_packs_expiration,
|
||||
do: expiration(default: :timer.seconds(5 * 60), interval: :timer.seconds(60))
|
||||
|
||||
defp idempotency_expiration,
|
||||
do: expiration(default: :timer.seconds(6 * 60 * 60), interval: :timer.seconds(60))
|
||||
|
||||
defp chat_message_id_idempotency_key_expiration,
|
||||
do: expiration(default: :timer.minutes(2), interval: :timer.seconds(60))
|
||||
|
||||
defp seconds_valid_interval,
|
||||
do: :timer.seconds(Config.get!([Pleroma.Captcha, :seconds_valid]))
|
||||
|
||||
@spec build_cachex(String.t(), keyword()) :: map()
|
||||
def build_cachex(type, opts),
|
||||
do: %{
|
||||
id: String.to_atom("cachex_" <> type),
|
||||
start: {Cachex, :start_link, [String.to_atom(type <> "_cache"), opts]},
|
||||
type: :worker
|
||||
}
|
||||
|
||||
defp chat_enabled?, do: Config.get([:chat, :enabled])
|
||||
|
||||
defp dont_run_in_test(env) when env in [:test, :benchmark], do: []
|
||||
|
||||
defp dont_run_in_test(_) do
|
||||
[
|
||||
{Registry,
|
||||
[
|
||||
name: Pleroma.Web.Streamer.registry(),
|
||||
keys: :duplicate,
|
||||
partitions: System.schedulers_online()
|
||||
]}
|
||||
] ++ background_migrators()
|
||||
end
|
||||
|
||||
defp background_migrators do
|
||||
[
|
||||
Pleroma.Migrators.HashtagsTableMigrator
|
||||
]
|
||||
end
|
||||
|
||||
defp chat_child(true) do
|
||||
[
|
||||
Pleroma.Web.ChatChannel.ChatChannelState,
|
||||
{Phoenix.PubSub, [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2]}
|
||||
]
|
||||
end
|
||||
|
||||
defp chat_child(_), do: []
|
||||
|
||||
defp task_children(:test) do
|
||||
[
|
||||
%{
|
||||
id: :web_push_init,
|
||||
start: {Task, :start_link, [&Pleroma.Web.Push.init/0]},
|
||||
restart: :temporary
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
defp task_children(_) do
|
||||
[
|
||||
%{
|
||||
id: :web_push_init,
|
||||
start: {Task, :start_link, [&Pleroma.Web.Push.init/0]},
|
||||
restart: :temporary
|
||||
},
|
||||
%{
|
||||
id: :internal_fetch_init,
|
||||
start: {Task, :start_link, [&Pleroma.Web.ActivityPub.InternalFetchActor.init/0]},
|
||||
restart: :temporary
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
# start hackney and gun pools in tests
|
||||
defp http_children(_, :test) do
|
||||
http_children(Tesla.Adapter.Hackney, nil) ++ http_children(Tesla.Adapter.Gun, nil)
|
||||
end
|
||||
|
||||
defp http_children(Tesla.Adapter.Hackney, _) do
|
||||
pools = [:federation, :media]
|
||||
|
||||
pools =
|
||||
if Config.get([Pleroma.Upload, :proxy_remote]) do
|
||||
[:upload | pools]
|
||||
else
|
||||
pools
|
||||
end
|
||||
|
||||
for pool <- pools do
|
||||
options = Config.get([:hackney_pools, pool])
|
||||
:hackney_pool.child_spec(pool, options)
|
||||
end
|
||||
end
|
||||
|
||||
defp http_children(Tesla.Adapter.Gun, _) do
|
||||
Pleroma.Gun.ConnectionPool.children() ++
|
||||
[{Task, &Pleroma.HTTP.AdapterHelper.Gun.limiter_setup/0}]
|
||||
end
|
||||
|
||||
defp http_children(_, _), do: []
|
||||
|
||||
@spec limiters_setup() :: :ok
|
||||
def limiters_setup do
|
||||
config = Config.get(ConcurrentLimiter, [])
|
||||
|
19
lib/pleroma/application/chat_supervisor.ex
Normal file
19
lib/pleroma/application/chat_supervisor.ex
Normal file
@ -0,0 +1,19 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Application.ChatSupervisor do
|
||||
use Supervisor
|
||||
|
||||
def start_link(_) do
|
||||
Supervisor.start_link(__MODULE__, :no_args)
|
||||
end
|
||||
|
||||
def init(_) do
|
||||
[
|
||||
Pleroma.Web.ChatChannel.ChatChannelState,
|
||||
{Phoenix.PubSub, [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2]}
|
||||
]
|
||||
|> Supervisor.init(strategy: :one_for_one)
|
||||
end
|
||||
end
|
244
lib/pleroma/application/config_dependent_deps.ex
Normal file
244
lib/pleroma/application/config_dependent_deps.ex
Normal file
@ -0,0 +1,244 @@
|
||||
# # Pleroma: A lightweight social networking server
|
||||
# # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# # SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Application.ConfigDependentDeps do
|
||||
use GenServer
|
||||
|
||||
require Logger
|
||||
|
||||
@config_path_mods_relation [
|
||||
{{:pleroma, :chat}, Pleroma.Application.ChatSupervisor},
|
||||
{{:pleroma, Oban}, Oban},
|
||||
{{:pleroma, :rate_limit}, Pleroma.Web.Plugs.RateLimiter.Supervisor},
|
||||
{{:pleroma, :streamer}, Pleroma.Web.Streamer.registry()},
|
||||
{{:pleroma, :pools}, Pleroma.Gun.GunSupervisor},
|
||||
{{:pleroma, :connections_pool}, Pleroma.Gun.GunSupervisor},
|
||||
{{:pleroma, :hackney_pools}, Pleroma.HTTP.HackneySupervisor},
|
||||
{{:pleroma, :gopher}, Pleroma.Gopher.Server},
|
||||
{{:pleroma, Pleroma.Captcha, [:seconds_valid]}, Pleroma.Web.Endpoint},
|
||||
{{:pleroma, Pleroma.Upload, [:proxy_remote]},
|
||||
Pleroma.Application.StartUpDependencies.adapter_module()},
|
||||
{{:pleroma, :instance, [:upload_limit]}, Pleroma.Web.Endpoint},
|
||||
{{:pleroma, :fed_sockets, [:enabled]}, Pleroma.Web.Endpoint},
|
||||
{:eshhd, :eshhd},
|
||||
{:ex_aws, :ex_aws}
|
||||
]
|
||||
|
||||
def start_link(opts) do
|
||||
opts = Keyword.put_new(opts, :relations, @config_path_mods_relation)
|
||||
|
||||
GenServer.start_link(__MODULE__, opts, name: opts[:name] || __MODULE__)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(opts) do
|
||||
init_state = %{
|
||||
dynamic_supervisor: opts[:dynamic_supervisor],
|
||||
relations: opts[:relations],
|
||||
reboot_paths: [],
|
||||
pids: %{}
|
||||
}
|
||||
|
||||
{:ok, init_state}
|
||||
end
|
||||
|
||||
def start_dependency(module, server \\ __MODULE__) do
|
||||
GenServer.call(server, {:start_dependency, module})
|
||||
end
|
||||
|
||||
def need_reboot?(server \\ __MODULE__) do
|
||||
GenServer.call(server, :need_reboot?)
|
||||
end
|
||||
|
||||
def restart_dependencies(server \\ __MODULE__) do
|
||||
GenServer.call(server, :restart_dependencies)
|
||||
end
|
||||
|
||||
def clear_state(server \\ __MODULE__) do
|
||||
GenServer.call(server, :clear_state)
|
||||
end
|
||||
|
||||
def save_config_paths_for_restart(changes, server \\ __MODULE__) do
|
||||
GenServer.call(server, {:save_config_paths, changes})
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:start_dependency, module}, _, state) do
|
||||
{result, state} =
|
||||
with {pid, state} when is_pid(pid) <- start_module(module, state) do
|
||||
{{:ok, pid}, state}
|
||||
else
|
||||
error -> {error, state}
|
||||
end
|
||||
|
||||
{:reply, result, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:need_reboot?, _, state) do
|
||||
{:reply, state[:reboot_paths] != [], state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:restart_dependencies, _, state) do
|
||||
{paths, state} = Map.get_and_update(state, :reboot_paths, &{&1, []})
|
||||
started_apps = Application.started_applications()
|
||||
|
||||
{result, state} =
|
||||
Enum.reduce_while(paths, {:ok, state}, fn
|
||||
path, {:ok, acc} when is_tuple(path) ->
|
||||
case restart(path, acc, acc[:pids][path], with_terminate: true) do
|
||||
{pid, state} when is_pid(pid) ->
|
||||
{:cont, {:ok, state}}
|
||||
|
||||
:ignore ->
|
||||
Logger.info("path #{inspect(path)} is ignored.")
|
||||
{:cont, {:ok, acc}}
|
||||
|
||||
error ->
|
||||
{:halt, {error, acc}}
|
||||
end
|
||||
|
||||
app, {:ok, acc}
|
||||
when is_atom(app) and app not in [:logger, :quack, :pleroma, :prometheus, :postgrex] ->
|
||||
restart_app(app, started_apps)
|
||||
{:cont, {:ok, acc}}
|
||||
end)
|
||||
|
||||
{:reply, result, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:clear_state, _, state) do
|
||||
state =
|
||||
state
|
||||
|> Map.put(:reboot_paths, [])
|
||||
|> Map.put(:pids, %{})
|
||||
|
||||
{:reply, :ok, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:save_config_paths, changes}, _, state) do
|
||||
paths =
|
||||
Enum.reduce(changes, state[:reboot_paths], fn
|
||||
%{group: group, key: key, value: value}, acc ->
|
||||
with {path, _} <- find_relation(state[:relations], group, key, value) do
|
||||
if path not in acc do
|
||||
[path | acc]
|
||||
else
|
||||
acc
|
||||
end
|
||||
else
|
||||
_ ->
|
||||
acc
|
||||
end
|
||||
end)
|
||||
|
||||
{:reply, paths, put_in(state[:reboot_paths], paths)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:DOWN, _ref, :process, pid, _reason}, state) do
|
||||
updated_state =
|
||||
with {path, ^pid} <-
|
||||
Enum.find(state[:pids], fn {_, registered_pid} -> registered_pid == pid end) do
|
||||
{_new_pid, new_state} = restart(path, state, pid)
|
||||
new_state
|
||||
else
|
||||
_ -> state
|
||||
end
|
||||
|
||||
{:noreply, updated_state}
|
||||
end
|
||||
|
||||
defp start_module(module, state) do
|
||||
with {:ok, relations} <- find_relations(state[:relations], module) do
|
||||
start_module(module, relations, state)
|
||||
end
|
||||
end
|
||||
|
||||
defp start_module(module, relations, state) do
|
||||
spec =
|
||||
module
|
||||
|> Pleroma.Application.StartUpDependencies.spec()
|
||||
|> Supervisor.child_spec(restart: :temporary)
|
||||
|
||||
with {:ok, pid} <-
|
||||
DynamicSupervisor.start_child(
|
||||
state[:dynamic_supervisor],
|
||||
spec
|
||||
) do
|
||||
pids = Map.new(relations, fn {path, _} -> {path, pid} end)
|
||||
Process.monitor(pid)
|
||||
{pid, put_in(state[:pids], Map.merge(state[:pids], pids))}
|
||||
end
|
||||
end
|
||||
|
||||
defp restart(path, state, pid, opts \\ [])
|
||||
|
||||
defp restart(path, state, nil, _) do
|
||||
with {_, module} <- find_relation(state[:relations], path) do
|
||||
start_module(module, state)
|
||||
end
|
||||
end
|
||||
|
||||
defp restart(path, state, pid, opts) when is_pid(pid) do
|
||||
with {_, module} <- find_relation(state[:relations], path),
|
||||
{:ok, relations} <- find_relations(state[:relations], module) do
|
||||
if opts[:with_terminate] do
|
||||
:ok = DynamicSupervisor.terminate_child(state[:dynamic_supervisor], pid)
|
||||
end
|
||||
|
||||
paths_for_remove = Enum.map(relations, fn {path, _} -> path end)
|
||||
state = put_in(state[:pids], Map.drop(state[:pids], paths_for_remove))
|
||||
|
||||
start_module(module, relations, state)
|
||||
end
|
||||
end
|
||||
|
||||
defp restart_app(app, started_applications) do
|
||||
with {^app, _, _} <- List.keyfind(started_applications, app, 0) do
|
||||
:ok = Application.stop(app)
|
||||
:ok = Application.start(app)
|
||||
else
|
||||
nil ->
|
||||
Logger.info("#{app} is not started.")
|
||||
|
||||
error ->
|
||||
error
|
||||
|> inspect()
|
||||
|> Logger.error()
|
||||
end
|
||||
end
|
||||
|
||||
defp find_relations(relations, module) do
|
||||
case Enum.filter(relations, fn {_, mod} -> mod == module end) do
|
||||
[] ->
|
||||
{:error, :relations_not_found}
|
||||
|
||||
relations ->
|
||||
{:ok, relations}
|
||||
end
|
||||
end
|
||||
|
||||
defp find_relation(relations, group, key, value) do
|
||||
Enum.find(relations, fn
|
||||
{g, _} when is_atom(g) ->
|
||||
g == group
|
||||
|
||||
{{g, k}, _} ->
|
||||
g == group and k == key
|
||||
|
||||
{{g, k, subkeys}, _} ->
|
||||
g == group and k == key and Enum.any?(Keyword.keys(value), &(&1 in subkeys))
|
||||
end)
|
||||
end
|
||||
|
||||
def find_relation(relations, path) do
|
||||
with nil <- Enum.find(relations, fn {key, _} -> key == path end) do
|
||||
{:error, :relation_not_found}
|
||||
end
|
||||
end
|
||||
end
|
105
lib/pleroma/application/environment.ex
Normal file
105
lib/pleroma/application/environment.ex
Normal file
@ -0,0 +1,105 @@
|
||||
# # Pleroma: A lightweight social networking server
|
||||
# # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# # SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Application.Environment do
|
||||
@moduledoc """
|
||||
Overwrites environment config with settings from config file or database.
|
||||
"""
|
||||
|
||||
require Logger
|
||||
|
||||
@doc """
|
||||
Method is called on pleroma start.
|
||||
Config dependent parts don't require restart, because are not started yet.
|
||||
But started apps need restart.
|
||||
"""
|
||||
@spec load_from_db_and_update() :: :ok
|
||||
def load_from_db_and_update do
|
||||
Pleroma.ConfigDB.all()
|
||||
|> update(restart_apps: true)
|
||||
end
|
||||
|
||||
@spec update(Path.t()) :: :ok
|
||||
def update(config_path) when is_binary(config_path) do
|
||||
config_path
|
||||
|> Pleroma.Config.Loader.read!()
|
||||
|> Application.put_all_env()
|
||||
end
|
||||
|
||||
@spec update([Pleroma.ConfigDB.t()], keyword()) :: :ok
|
||||
def update(changes, opts \\ []) when is_list(changes) do
|
||||
if Pleroma.Config.get(:configurable_from_database) do
|
||||
defaults = Pleroma.Config.Holder.default_config()
|
||||
|
||||
changes
|
||||
|> filter_logger()
|
||||
|> prepare_logger_changes(defaults)
|
||||
|> Enum.each(&configure_logger/1)
|
||||
|
||||
changes
|
||||
|> Pleroma.ConfigDB.merge_changes_with_defaults(defaults)
|
||||
|> Enum.each(&update_env(&1))
|
||||
|
||||
if opts[:restart_apps] do
|
||||
# restart only apps on pleroma start
|
||||
changes
|
||||
|> Enum.filter(fn %{group: group} ->
|
||||
group not in [:logger, :quack, :pleroma, :prometheus, :postgrex]
|
||||
end)
|
||||
|> Pleroma.Application.ConfigDependentDeps.save_config_paths_for_restart()
|
||||
|
||||
Pleroma.Application.ConfigDependentDeps.restart_dependencies()
|
||||
else
|
||||
Pleroma.Application.ConfigDependentDeps.save_config_paths_for_restart(changes)
|
||||
end
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp filter_logger(changes) do
|
||||
Enum.filter(changes, fn %{group: group} -> group in [:logger, :quack] end)
|
||||
end
|
||||
|
||||
defp prepare_logger_changes(changes, defaults) do
|
||||
Enum.map(changes, fn %{group: group} = change ->
|
||||
{change, Pleroma.ConfigDB.merge_change_value_with_default(change, defaults[group])}
|
||||
end)
|
||||
end
|
||||
|
||||
defp configure_logger({%{group: :quack}, merged_value}) do
|
||||
Logger.configure_backend(Quack.Logger, merged_value)
|
||||
end
|
||||
|
||||
defp configure_logger({%{group: :logger} = change, merged_value}) do
|
||||
if change.value[:backends] do
|
||||
Enum.each(Application.get_env(:logger, :backends), &Logger.remove_backend/1)
|
||||
|
||||
Enum.each(merged_value[:backends], &Logger.add_backend/1)
|
||||
end
|
||||
|
||||
if change.value[:console] do
|
||||
console = merged_value[:console]
|
||||
console = put_in(console[:format], console[:format] <> "\n")
|
||||
|
||||
Logger.configure_backend(:console, console)
|
||||
end
|
||||
|
||||
if change.value[:ex_syslogger] do
|
||||
Logger.configure_backend({ExSyslogger, :ex_syslogger}, merged_value[:ex_syslogger])
|
||||
end
|
||||
|
||||
Logger.configure(merged_value)
|
||||
end
|
||||
|
||||
defp update_env(%{group: group, key: key, value: nil}), do: Application.delete_env(group, key)
|
||||
|
||||
defp update_env(%{group: group, value: config} = change) do
|
||||
if group in Pleroma.ConfigDB.groups_without_keys() do
|
||||
Application.put_all_env([{group, config}])
|
||||
else
|
||||
Application.put_env(group, change.key, config)
|
||||
end
|
||||
end
|
||||
end
|
@ -2,7 +2,7 @@
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.ApplicationRequirements do
|
||||
defmodule Pleroma.Application.Requirements do
|
||||
@moduledoc """
|
||||
The module represents the collection of validations to runs before start server.
|
||||
"""
|
||||
@ -18,6 +18,8 @@ defmodule Pleroma.ApplicationRequirements do
|
||||
|
||||
@spec verify!() :: :ok | VerifyError.t()
|
||||
def verify! do
|
||||
adapter = Application.get_env(:tesla, :adapter)
|
||||
|
||||
:ok
|
||||
|> check_system_commands!()
|
||||
|> check_confirmation_accounts!()
|
||||
@ -25,11 +27,12 @@ defmodule Pleroma.ApplicationRequirements do
|
||||
|> check_welcome_message_config!()
|
||||
|> check_rum!()
|
||||
|> check_repo_pool_size!()
|
||||
|> handle_result()
|
||||
|> check_otp_version!(adapter)
|
||||
|> handle_result!()
|
||||
end
|
||||
|
||||
defp handle_result(:ok), do: :ok
|
||||
defp handle_result({:error, message}), do: raise(VerifyError, message: message)
|
||||
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
|
||||
@ -160,9 +163,9 @@ defmodule Pleroma.ApplicationRequirements do
|
||||
|
||||
defp check_system_commands!(:ok) do
|
||||
filter_commands_statuses = [
|
||||
check_filter(Pleroma.Upload.Filters.Exiftool, "exiftool"),
|
||||
check_filter(Pleroma.Upload.Filters.Mogrify, "mogrify"),
|
||||
check_filter(Pleroma.Upload.Filters.Mogrifun, "mogrify")
|
||||
check_filter!(Pleroma.Upload.Filters.Exiftool, "exiftool"),
|
||||
check_filter!(Pleroma.Upload.Filters.Mogrify, "mogrify"),
|
||||
check_filter!(Pleroma.Upload.Filters.Mogrifun, "mogrify")
|
||||
]
|
||||
|
||||
preview_proxy_commands_status =
|
||||
@ -213,7 +216,7 @@ defmodule Pleroma.ApplicationRequirements do
|
||||
|
||||
defp check_repo_pool_size!(result), do: result
|
||||
|
||||
defp check_filter(filter, command_required) do
|
||||
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
|
||||
@ -227,4 +230,32 @@ defmodule Pleroma.ApplicationRequirements do
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
defp check_otp_version!(:ok, Tesla.Adapter.Gun) do
|
||||
if version = Pleroma.OTPVersion.version() do
|
||||
[major, minor] =
|
||||
version
|
||||
|> String.split(".")
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
|> Enum.take(2)
|
||||
|
||||
if (major == 22 and minor < 2) or major < 22 do
|
||||
Logger.error("
|
||||
!!!OTP VERSION ERROR!!!
|
||||
You are using gun adapter with OTP version #{version}, which doesn't support correct handling of unordered certificates chains. Please update your Erlang/OTP to at least 22.2.
|
||||
")
|
||||
{:error, "OTP version error"}
|
||||
else
|
||||
:ok
|
||||
end
|
||||
else
|
||||
Logger.error("
|
||||
!!!OTP VERSION ERROR!!!
|
||||
To support correct handling of unordered certificates chains - OTP version must be > 22.2.
|
||||
")
|
||||
{:error, "OTP version error"}
|
||||
end
|
||||
end
|
||||
|
||||
defp check_otp_version!(result, _), do: result
|
||||
end
|
182
lib/pleroma/application/start_up_dependencies.ex
Normal file
182
lib/pleroma/application/start_up_dependencies.ex
Normal file
@ -0,0 +1,182 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Application.StartUpDependencies do
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Web.Endpoint
|
||||
|
||||
require Cachex.Spec
|
||||
require Logger
|
||||
|
||||
@type config_path() :: {atom(), atom()} | {atom(), atom(), [atom()]}
|
||||
@type relation() :: {config_path(), module()}
|
||||
|
||||
@spec start_all(Pleroma.Application.env()) ::
|
||||
:ok | {:error, {:already_started, pid()} | :max_children | term()}
|
||||
def start_all(env) do
|
||||
with :ok <- start_common_deps(env),
|
||||
:ok <- start_config_dependent_deps(env) do
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
@spec adapter_module() :: module()
|
||||
def adapter_module do
|
||||
if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Gun do
|
||||
Pleroma.Gun.GunSupervisor
|
||||
else
|
||||
Pleroma.HTTP.HackneySupervisor
|
||||
end
|
||||
end
|
||||
|
||||
@spec spec(module()) :: module() | {module(), keyword()}
|
||||
def spec(Oban), do: {Oban, Config.get(Oban)}
|
||||
|
||||
def spec(Pleroma.Web.StreamerRegistry) do
|
||||
{Registry,
|
||||
[
|
||||
name: Pleroma.Web.Streamer.registry(),
|
||||
keys: :duplicate,
|
||||
partitions: System.schedulers_online()
|
||||
]}
|
||||
end
|
||||
|
||||
def spec(child), do: child
|
||||
|
||||
@spec cachex_spec({String.t(), keyword()}) :: :supervisor.child_spec()
|
||||
def cachex_spec({type, opts}) do
|
||||
%{
|
||||
id: String.to_atom("cachex_" <> type),
|
||||
start: {Cachex, :start_link, [String.to_atom(type <> "_cache"), opts]},
|
||||
type: :worker
|
||||
}
|
||||
end
|
||||
|
||||
defp start_common_deps(env) do
|
||||
fun = fn child ->
|
||||
DynamicSupervisor.start_child(Pleroma.Application.dynamic_supervisor(), spec(child))
|
||||
end
|
||||
|
||||
[
|
||||
Pleroma.Emoji,
|
||||
Pleroma.Stats,
|
||||
Pleroma.JobQueueMonitor,
|
||||
{Majic.Pool, [name: Pleroma.MajicPool, pool_size: Config.get([:majic_pool, :size], 2)]},
|
||||
%{
|
||||
id: :web_push_init,
|
||||
start: {Task, :start_link, [&Pleroma.Web.Push.init/0]},
|
||||
restart: :temporary
|
||||
}
|
||||
]
|
||||
|> add_cachex_deps()
|
||||
|> maybe_add_init_internal_fetch_actor_task(env)
|
||||
|> maybe_add_background_migrator(env)
|
||||
|> start_while(fun)
|
||||
end
|
||||
|
||||
defp start_config_dependent_deps(env) do
|
||||
fun = fn child -> Pleroma.Application.ConfigDependentDeps.start_dependency(child) end
|
||||
|
||||
[
|
||||
Pleroma.Web.Plugs.RateLimiter.Supervisor,
|
||||
Oban,
|
||||
Endpoint,
|
||||
Pleroma.Gopher.Server
|
||||
]
|
||||
|> add_http_children(env)
|
||||
|> maybe_add(:streamer, env)
|
||||
|> maybe_add_chat_child()
|
||||
|> start_while(fun)
|
||||
end
|
||||
|
||||
defp start_while(deps, fun) do
|
||||
Enum.reduce_while(deps, :ok, fn child, acc ->
|
||||
case fun.(child) do
|
||||
{:ok, _} ->
|
||||
{:cont, acc}
|
||||
|
||||
# consider this behavior is normal
|
||||
:ignore ->
|
||||
Logger.info("#{inspect(child)} is ignored.")
|
||||
{:cont, acc}
|
||||
|
||||
error ->
|
||||
Logger.error("Child #{inspect(child)} can't be started. #{inspect(error)}")
|
||||
{:halt, error}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@spec cachex_deps() :: [tuple()]
|
||||
def cachex_deps do
|
||||
captcha_clean_up_interval =
|
||||
[Pleroma.Captcha, :seconds_valid]
|
||||
|> Config.get!()
|
||||
|> :timer.seconds()
|
||||
|
||||
[
|
||||
{"used_captcha", expiration: Cachex.Spec.expiration(interval: captcha_clean_up_interval)},
|
||||
{"user", expiration: cachex_expiration(25_000, 1000), limit: 2500},
|
||||
{"object", expiration: cachex_expiration(25_000, 1000), limit: 2500},
|
||||
{"rich_media",
|
||||
expiration: Cachex.Spec.expiration(default: :timer.minutes(120)), limit: 5000},
|
||||
{"scrubber", limit: 2500},
|
||||
{"idempotency", expiration: cachex_expiration(21_600, 60), limit: 2500},
|
||||
{"web_resp", limit: 2500},
|
||||
{"emoji_packs", expiration: cachex_expiration(300, 60), limit: 10},
|
||||
{"failed_proxy_url", limit: 2500},
|
||||
{"banned_urls",
|
||||
expiration: Cachex.Spec.expiration(default: :timer.hours(24 * 30)), limit: 5_000},
|
||||
{"chat_message_id_idempotency_key",
|
||||
expiration: cachex_expiration(:timer.minutes(2), :timer.seconds(60)), limit: 500_000}
|
||||
]
|
||||
end
|
||||
|
||||
defp add_cachex_deps(application_deps) do
|
||||
cachex_deps()
|
||||
|> Enum.reduce(application_deps, fn cachex_init_args, acc ->
|
||||
[cachex_spec(cachex_init_args) | acc]
|
||||
end)
|
||||
end
|
||||
|
||||
defp cachex_expiration(default, interval) do
|
||||
Cachex.Spec.expiration(default: :timer.seconds(default), interval: :timer.seconds(interval))
|
||||
end
|
||||
|
||||
defp maybe_add_init_internal_fetch_actor_task(children, :test), do: children
|
||||
|
||||
defp maybe_add_init_internal_fetch_actor_task(children, _) do
|
||||
[
|
||||
%{
|
||||
id: :internal_fetch_init,
|
||||
start: {Task, :start_link, [&Pleroma.Web.ActivityPub.InternalFetchActor.init/0]},
|
||||
restart: :temporary
|
||||
}
|
||||
| children
|
||||
]
|
||||
end
|
||||
|
||||
defp maybe_add_background_migrator(children, env) when env in [:test, :benchmark], do: children
|
||||
|
||||
defp maybe_add_background_migrator(children, _) do
|
||||
[Pleroma.Migrators.HashtagsTableMigrator | children]
|
||||
end
|
||||
|
||||
defp maybe_add(children, _, env) when env in [:test, :benchmark], do: children
|
||||
defp maybe_add(children, :streamer, _), do: [Pleroma.Web.Streamer.registry() | children]
|
||||
|
||||
defp add_http_children(children, :test) do
|
||||
[Pleroma.HTTP.HackneySupervisor, Pleroma.Gun.GunSupervisor | children]
|
||||
end
|
||||
|
||||
defp add_http_children(children, _), do: [adapter_module() | children]
|
||||
|
||||
defp maybe_add_chat_child(children) do
|
||||
if Config.get([:chat, :enabled]) do
|
||||
[Pleroma.Application.ChatSupervisor | children]
|
||||
else
|
||||
children
|
||||
end
|
||||
end
|
||||
end
|
195
lib/pleroma/config/converter.ex
Normal file
195
lib/pleroma/config/converter.ex
Normal file
@ -0,0 +1,195 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Config.Converter do
|
||||
@moduledoc """
|
||||
Converts json structures with strings into elixir structures and types and vice versa.
|
||||
"""
|
||||
@spec to_elixir_types(boolean() | String.t() | map() | list()) :: term()
|
||||
def to_elixir_types(%{"tuple" => [":args", args]}) when is_list(args) do
|
||||
arguments =
|
||||
Enum.map(args, fn arg ->
|
||||
if String.contains?(arg, ["{", "}"]) do
|
||||
{elem, []} = Code.eval_string(arg)
|
||||
elem
|
||||
else
|
||||
to_elixir_types(arg)
|
||||
end
|
||||
end)
|
||||
|
||||
{:args, arguments}
|
||||
end
|
||||
|
||||
def to_elixir_types(%{"tuple" => [":proxy_url", %{"tuple" => [type, host, port]}]}) do
|
||||
{:proxy_url, {string_to_elixir_types!(type), parse_host(host), port}}
|
||||
end
|
||||
|
||||
def to_elixir_types(%{"tuple" => [":partial_chain", entity]}) do
|
||||
{partial_chain, []} =
|
||||
entity
|
||||
|> String.replace(~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "")
|
||||
|> Code.eval_string()
|
||||
|
||||
{:partial_chain, partial_chain}
|
||||
end
|
||||
|
||||
def to_elixir_types(%{"tuple" => entity}) do
|
||||
Enum.reduce(entity, {}, &Tuple.append(&2, to_elixir_types(&1)))
|
||||
end
|
||||
|
||||
def to_elixir_types(entity) when is_map(entity) do
|
||||
Map.new(entity, fn {k, v} -> {to_elixir_types(k), to_elixir_types(v)} end)
|
||||
end
|
||||
|
||||
def to_elixir_types(entity) when is_list(entity) do
|
||||
Enum.map(entity, &to_elixir_types/1)
|
||||
end
|
||||
|
||||
def to_elixir_types(entity) when is_binary(entity) do
|
||||
entity
|
||||
|> String.trim()
|
||||
|> string_to_elixir_types!()
|
||||
end
|
||||
|
||||
def to_elixir_types(entity), do: entity
|
||||
|
||||
defp parse_host("localhost"), do: :localhost
|
||||
|
||||
defp parse_host(host) do
|
||||
charlist = to_charlist(host)
|
||||
|
||||
case :inet.parse_address(charlist) do
|
||||
{:error, :einval} ->
|
||||
charlist
|
||||
|
||||
{:ok, ip} ->
|
||||
ip
|
||||
end
|
||||
end
|
||||
|
||||
@spec string_to_elixir_types!(String.t()) ::
|
||||
atom() | Regex.t() | module() | String.t() | no_return()
|
||||
def string_to_elixir_types!("~r" <> _pattern = regex) do
|
||||
pattern =
|
||||
~r/^~r(?'delimiter'[\/|"'([{<]{1})(?'pattern'.+)[\/|"')\]}>]{1}(?'modifier'[uismxfU]*)/u
|
||||
|
||||
delimiters = ["/", "|", "\"", "'", {"(", ")"}, {"[", "]"}, {"{", "}"}, {"<", ">"}]
|
||||
|
||||
with %{"modifier" => modifier, "pattern" => pattern, "delimiter" => regex_delimiter} <-
|
||||
Regex.named_captures(pattern, regex),
|
||||
{:ok, {leading, closing}} <- find_valid_delimiter(delimiters, pattern, regex_delimiter),
|
||||
{result, _} <- Code.eval_string("~r#{leading}#{pattern}#{closing}#{modifier}") do
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
def string_to_elixir_types!(":" <> atom), do: String.to_atom(atom)
|
||||
|
||||
def string_to_elixir_types!(value) do
|
||||
if module_name?(value) do
|
||||
String.to_existing_atom("Elixir." <> value)
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
defp find_valid_delimiter([], _string, _) do
|
||||
raise(ArgumentError, message: "valid delimiter for Regex expression not found")
|
||||
end
|
||||
|
||||
defp find_valid_delimiter([{leading, closing} = delimiter | others], pattern, regex_delimiter)
|
||||
when is_tuple(delimiter) do
|
||||
if String.contains?(pattern, closing) do
|
||||
find_valid_delimiter(others, pattern, regex_delimiter)
|
||||
else
|
||||
{:ok, {leading, closing}}
|
||||
end
|
||||
end
|
||||
|
||||
defp find_valid_delimiter([delimiter | others], pattern, regex_delimiter) do
|
||||
if String.contains?(pattern, delimiter) do
|
||||
find_valid_delimiter(others, pattern, regex_delimiter)
|
||||
else
|
||||
{:ok, {delimiter, delimiter}}
|
||||
end
|
||||
end
|
||||
|
||||
@spec module_name?(String.t()) :: boolean()
|
||||
def module_name?(string) do
|
||||
Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack|Ueberauth|Swoosh)\./, string) or
|
||||
string in ["Oban", "Ueberauth", "ExSyslogger"]
|
||||
end
|
||||
|
||||
@spec to_json_types(term()) :: map() | list() | boolean() | String.t() | integer()
|
||||
def to_json_types(entity) when is_list(entity) do
|
||||
Enum.map(entity, &to_json_types/1)
|
||||
end
|
||||
|
||||
def to_json_types(%Regex{} = entity), do: inspect(entity)
|
||||
|
||||
def to_json_types(entity) when is_map(entity) do
|
||||
Map.new(entity, fn {k, v} -> {to_json_types(k), to_json_types(v)} end)
|
||||
end
|
||||
|
||||
def to_json_types({:args, args}) when is_list(args) do
|
||||
arguments =
|
||||
Enum.map(args, fn
|
||||
arg when is_tuple(arg) -> inspect(arg)
|
||||
arg -> to_json_types(arg)
|
||||
end)
|
||||
|
||||
%{"tuple" => [":args", arguments]}
|
||||
end
|
||||
|
||||
def to_json_types({:proxy_url, {type, :localhost, port}}) do
|
||||
%{"tuple" => [":proxy_url", %{"tuple" => [to_json_types(type), "localhost", port]}]}
|
||||
end
|
||||
|
||||
def to_json_types({:proxy_url, {type, host, port}}) when is_tuple(host) do
|
||||
ip =
|
||||
host
|
||||
|> :inet_parse.ntoa()
|
||||
|> to_string()
|
||||
|
||||
%{
|
||||
"tuple" => [
|
||||
":proxy_url",
|
||||
%{"tuple" => [to_json_types(type), ip, port]}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def to_json_types({:proxy_url, {type, host, port}}) do
|
||||
%{
|
||||
"tuple" => [
|
||||
":proxy_url",
|
||||
%{"tuple" => [to_json_types(type), to_string(host), port]}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def to_json_types({:partial_chain, entity}),
|
||||
do: %{"tuple" => [":partial_chain", inspect(entity)]}
|
||||
|
||||
def to_json_types(entity) when is_tuple(entity) do
|
||||
value =
|
||||
entity
|
||||
|> Tuple.to_list()
|
||||
|> to_json_types()
|
||||
|
||||
%{"tuple" => value}
|
||||
end
|
||||
|
||||
def to_json_types(entity) when is_binary(entity), do: entity
|
||||
|
||||
def to_json_types(entity) when is_boolean(entity) or is_number(entity) or is_nil(entity) do
|
||||
entity
|
||||
end
|
||||
|
||||
def to_json_types(entity) when entity in [:"tlsv1.1", :"tlsv1.2", :"tlsv1.3"] do
|
||||
":#{entity}"
|
||||
end
|
||||
|
||||
def to_json_types(entity) when is_atom(entity), do: inspect(entity)
|
||||
end
|
@ -41,7 +41,8 @@ defmodule Pleroma.Config.DeprecationWarnings do
|
||||
:ok <- check_gun_pool_options(),
|
||||
:ok <- check_activity_expiration_config(),
|
||||
:ok <- check_remote_ip_plug_name(),
|
||||
:ok <- check_uploders_s3_public_endpoint() do
|
||||
:ok <- check_uploders_s3_public_endpoint(),
|
||||
:ok <- check_oban_config() do
|
||||
:ok
|
||||
else
|
||||
_ ->
|
||||
@ -79,7 +80,7 @@ defmodule Pleroma.Config.DeprecationWarnings do
|
||||
move_namespace_and_warn(@mrf_config_map, warning_preface)
|
||||
end
|
||||
|
||||
@spec move_namespace_and_warn([config_map()], String.t()) :: :ok | nil
|
||||
@spec move_namespace_and_warn([config_map()], String.t()) :: :ok | :error
|
||||
def move_namespace_and_warn(config_map, warning_preface) do
|
||||
warning =
|
||||
Enum.reduce(config_map, "", fn
|
||||
@ -102,7 +103,7 @@ defmodule Pleroma.Config.DeprecationWarnings do
|
||||
end
|
||||
end
|
||||
|
||||
@spec check_media_proxy_whitelist_config() :: :ok | nil
|
||||
@spec check_media_proxy_whitelist_config() :: :ok | :error
|
||||
def check_media_proxy_whitelist_config do
|
||||
whitelist = Config.get([:media_proxy, :whitelist])
|
||||
|
||||
@ -163,7 +164,7 @@ defmodule Pleroma.Config.DeprecationWarnings do
|
||||
end
|
||||
end
|
||||
|
||||
@spec check_activity_expiration_config() :: :ok | nil
|
||||
@spec check_activity_expiration_config() :: :ok | :error
|
||||
def check_activity_expiration_config do
|
||||
warning_preface = """
|
||||
!!!DEPRECATION WARNING!!!
|
||||
@ -215,4 +216,41 @@ defmodule Pleroma.Config.DeprecationWarnings do
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
@spec check_oban_config() :: :ok | :error
|
||||
def check_oban_config do
|
||||
oban_config = Config.get(Oban)
|
||||
|
||||
{crontab, changed?} =
|
||||
[
|
||||
Pleroma.Workers.Cron.StatsWorker,
|
||||
Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker,
|
||||
Pleroma.Workers.Cron.ClearOauthTokenWorker
|
||||
]
|
||||
|> Enum.reduce({oban_config[:crontab], false}, fn removed_worker, {acc, changed?} ->
|
||||
with acc when is_list(acc) <- acc,
|
||||
setting when is_tuple(setting) <-
|
||||
Enum.find(acc, fn {_, worker} -> worker == removed_worker end) do
|
||||
"""
|
||||
!!!OBAN CONFIG WARNING!!!
|
||||
You are using old workers in Oban crontab settings, which were removed.
|
||||
Please, remove setting from crontab in your config file (prod.secret.exs): #{
|
||||
inspect(setting)
|
||||
}
|
||||
"""
|
||||
|> Logger.warn()
|
||||
|
||||
{List.delete(acc, setting), true}
|
||||
else
|
||||
_ -> {acc, changed?}
|
||||
end
|
||||
end)
|
||||
|
||||
if changed? do
|
||||
Config.put(Oban, Keyword.put(oban_config, :crontab, crontab))
|
||||
:error
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -3,57 +3,74 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Config.Loader do
|
||||
@reject_groups [
|
||||
:postgrex,
|
||||
:tesla,
|
||||
:phoenix,
|
||||
:tzdata,
|
||||
:http_signatures,
|
||||
:web_push_encryption,
|
||||
:floki,
|
||||
:pbkdf2_elixir
|
||||
]
|
||||
|
||||
@reject_keys [
|
||||
Pleroma.Repo,
|
||||
Pleroma.Web.Endpoint,
|
||||
Pleroma.InstallerWeb.Endpoint,
|
||||
:env,
|
||||
:configurable_from_database,
|
||||
:database,
|
||||
:swarm
|
||||
]
|
||||
|
||||
@reject_groups [
|
||||
:postgrex,
|
||||
:tesla
|
||||
:ecto_repos,
|
||||
Pleroma.Gun,
|
||||
Pleroma.ReverseProxy.Client,
|
||||
Pleroma.Web.Auth.Authenticator
|
||||
]
|
||||
|
||||
if Code.ensure_loaded?(Config.Reader) do
|
||||
@reader Config.Reader
|
||||
|
||||
def read(path), do: @reader.read!(path)
|
||||
@config_header "import Config\r\n\r\n"
|
||||
else
|
||||
# support for Elixir less than 1.9
|
||||
@reader Mix.Config
|
||||
def read(path) do
|
||||
path
|
||||
|> @reader.eval!()
|
||||
|> elem(0)
|
||||
end
|
||||
@config_header "use Mix.Config\r\n\r\n"
|
||||
end
|
||||
|
||||
@spec read(Path.t()) :: keyword()
|
||||
@spec read!(Path.t()) :: keyword()
|
||||
def read!(path), do: @reader.read!(path)
|
||||
|
||||
@spec merge(keyword(), keyword()) :: keyword()
|
||||
def merge(c1, c2), do: @reader.merge(c1, c2)
|
||||
|
||||
@spec config_header() :: String.t()
|
||||
def config_header, do: @config_header
|
||||
|
||||
@spec default_config() :: keyword()
|
||||
def default_config do
|
||||
"config/config.exs"
|
||||
|> read()
|
||||
|> filter()
|
||||
config =
|
||||
"config/config.exs"
|
||||
|> read!()
|
||||
|> filter()
|
||||
|
||||
logger_config =
|
||||
:logger
|
||||
|> Application.get_all_env()
|
||||
|> Enum.filter(fn {key, _} -> key in [:backends, :console, :ex_syslogger] end)
|
||||
|
||||
merge(config, logger: logger_config)
|
||||
end
|
||||
|
||||
defp filter(configs) do
|
||||
configs
|
||||
|> Keyword.keys()
|
||||
|> Enum.reduce([], &Keyword.put(&2, &1, filter_group(&1, configs)))
|
||||
end
|
||||
@spec filter(keyword()) :: keyword()
|
||||
def filter(configs) do
|
||||
Enum.reduce(configs, [], fn
|
||||
{group, _settings}, group_acc when group in @reject_groups ->
|
||||
group_acc
|
||||
|
||||
@spec filter_group(atom(), keyword()) :: keyword()
|
||||
def filter_group(group, configs) do
|
||||
Enum.reject(configs[group], fn {key, _v} ->
|
||||
key in @reject_keys or group in @reject_groups or
|
||||
(group == :phoenix and key == :serve_endpoints)
|
||||
{group, settings}, group_acc ->
|
||||
Enum.reduce(settings, group_acc, fn
|
||||
{key, _value}, acc when key in @reject_keys -> acc
|
||||
setting, acc -> Keyword.update(acc, group, [setting], &Keyword.merge(&1, [setting]))
|
||||
end)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
@ -1,38 +0,0 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Config.Oban do
|
||||
require Logger
|
||||
|
||||
def warn do
|
||||
oban_config = Pleroma.Config.get(Oban)
|
||||
|
||||
crontab =
|
||||
[
|
||||
Pleroma.Workers.Cron.StatsWorker,
|
||||
Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker,
|
||||
Pleroma.Workers.Cron.ClearOauthTokenWorker
|
||||
]
|
||||
|> Enum.reduce(oban_config[:crontab], fn removed_worker, acc ->
|
||||
with acc when is_list(acc) <- acc,
|
||||
setting when is_tuple(setting) <-
|
||||
Enum.find(acc, fn {_, worker} -> worker == removed_worker end) do
|
||||
"""
|
||||
!!!OBAN CONFIG WARNING!!!
|
||||
You are using old workers in Oban crontab settings, which were removed.
|
||||
Please, remove setting from crontab in your config file (prod.secret.exs): #{
|
||||
inspect(setting)
|
||||
}
|
||||
"""
|
||||
|> Logger.warn()
|
||||
|
||||
List.delete(acc, setting)
|
||||
else
|
||||
_ -> acc
|
||||
end
|
||||
end)
|
||||
|
||||
Pleroma.Config.put(Oban, Keyword.put(oban_config, :crontab, crontab))
|
||||
end
|
||||
end
|
@ -1,201 +0,0 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Config.TransferTask do
|
||||
use Task
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.ConfigDB
|
||||
alias Pleroma.Repo
|
||||
|
||||
require Logger
|
||||
|
||||
@type env() :: :test | :benchmark | :dev | :prod
|
||||
|
||||
@reboot_time_keys [
|
||||
{:pleroma, :hackney_pools},
|
||||
{:pleroma, :chat},
|
||||
{:pleroma, Oban},
|
||||
{:pleroma, :rate_limit},
|
||||
{:pleroma, :markup},
|
||||
{:pleroma, :streamer},
|
||||
{:pleroma, :pools},
|
||||
{:pleroma, :connections_pool}
|
||||
]
|
||||
|
||||
@reboot_time_subkeys [
|
||||
{:pleroma, Pleroma.Captcha, [:seconds_valid]},
|
||||
{:pleroma, Pleroma.Upload, [:proxy_remote]},
|
||||
{:pleroma, :instance, [:upload_limit]},
|
||||
{:pleroma, :gopher, [:enabled]}
|
||||
]
|
||||
|
||||
def start_link(restart_pleroma? \\ true) do
|
||||
load_and_update_env([], restart_pleroma?)
|
||||
if Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Repo)
|
||||
:ignore
|
||||
end
|
||||
|
||||
@spec load_and_update_env([ConfigDB.t()], boolean()) :: :ok
|
||||
def load_and_update_env(deleted_settings \\ [], restart_pleroma? \\ true) do
|
||||
with {_, true} <- {:configurable, Config.get(:configurable_from_database)} do
|
||||
# We need to restart applications for loaded settings take effect
|
||||
|
||||
{logger, other} =
|
||||
(Repo.all(ConfigDB) ++ deleted_settings)
|
||||
|> Enum.map(&merge_with_default/1)
|
||||
|> Enum.split_with(fn {group, _, _, _} -> group in [:logger, :quack] end)
|
||||
|
||||
logger
|
||||
|> Enum.sort()
|
||||
|> Enum.each(&configure/1)
|
||||
|
||||
started_applications = Application.started_applications()
|
||||
|
||||
# TODO: some problem with prometheus after restart!
|
||||
reject = [nil, :prometheus, :postgrex]
|
||||
|
||||
reject =
|
||||
if restart_pleroma? do
|
||||
reject
|
||||
else
|
||||
[:pleroma | reject]
|
||||
end
|
||||
|
||||
other
|
||||
|> Enum.map(&update/1)
|
||||
|> Enum.uniq()
|
||||
|> Enum.reject(&(&1 in reject))
|
||||
|> maybe_set_pleroma_last()
|
||||
|> Enum.each(&restart(started_applications, &1, Config.get(:env)))
|
||||
|
||||
:ok
|
||||
else
|
||||
{:configurable, false} -> Restarter.Pleroma.rebooted()
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_set_pleroma_last(apps) do
|
||||
# to be ensured that pleroma will be restarted last
|
||||
if :pleroma in apps do
|
||||
apps
|
||||
|> List.delete(:pleroma)
|
||||
|> List.insert_at(-1, :pleroma)
|
||||
else
|
||||
Restarter.Pleroma.rebooted()
|
||||
apps
|
||||
end
|
||||
end
|
||||
|
||||
defp merge_with_default(%{group: group, key: key, value: value} = setting) do
|
||||
default = Config.Holder.default_config(group, key)
|
||||
|
||||
merged =
|
||||
cond do
|
||||
Ecto.get_meta(setting, :state) == :deleted -> default
|
||||
can_be_merged?(default, value) -> ConfigDB.merge_group(group, key, default, value)
|
||||
true -> value
|
||||
end
|
||||
|
||||
{group, key, value, merged}
|
||||
end
|
||||
|
||||
# change logger configuration in runtime, without restart
|
||||
defp configure({:quack, key, _, merged}) do
|
||||
Logger.configure_backend(Quack.Logger, [{key, merged}])
|
||||
:ok = update_env(:quack, key, merged)
|
||||
end
|
||||
|
||||
defp configure({_, :backends, _, merged}) do
|
||||
# removing current backends
|
||||
Enum.each(Application.get_env(:logger, :backends), &Logger.remove_backend/1)
|
||||
|
||||
Enum.each(merged, &Logger.add_backend/1)
|
||||
|
||||
:ok = update_env(:logger, :backends, merged)
|
||||
end
|
||||
|
||||
defp configure({_, key, _, merged}) when key in [:console, :ex_syslogger] do
|
||||
merged =
|
||||
if key == :console do
|
||||
put_in(merged[:format], merged[:format] <> "\n")
|
||||
else
|
||||
merged
|
||||
end
|
||||
|
||||
backend =
|
||||
if key == :ex_syslogger,
|
||||
do: {ExSyslogger, :ex_syslogger},
|
||||
else: key
|
||||
|
||||
Logger.configure_backend(backend, merged)
|
||||
:ok = update_env(:logger, key, merged)
|
||||
end
|
||||
|
||||
defp configure({_, key, _, merged}) do
|
||||
Logger.configure([{key, merged}])
|
||||
:ok = update_env(:logger, key, merged)
|
||||
end
|
||||
|
||||
defp update({group, key, value, merged}) do
|
||||
try do
|
||||
:ok = update_env(group, key, merged)
|
||||
|
||||
if group != :pleroma or pleroma_need_restart?(group, key, value), do: group
|
||||
rescue
|
||||
error ->
|
||||
error_msg =
|
||||
"updating env causes error, group: #{inspect(group)}, key: #{inspect(key)}, value: #{
|
||||
inspect(value)
|
||||
} error: #{inspect(error)}"
|
||||
|
||||
Logger.warn(error_msg)
|
||||
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defp update_env(group, key, nil), do: Application.delete_env(group, key)
|
||||
defp update_env(group, key, value), do: Application.put_env(group, key, value)
|
||||
|
||||
@spec pleroma_need_restart?(atom(), atom(), any()) :: boolean()
|
||||
def pleroma_need_restart?(group, key, value) do
|
||||
group_and_key_need_reboot?(group, key) or group_and_subkey_need_reboot?(group, key, value)
|
||||
end
|
||||
|
||||
defp group_and_key_need_reboot?(group, key) do
|
||||
Enum.any?(@reboot_time_keys, fn {g, k} -> g == group and k == key end)
|
||||
end
|
||||
|
||||
defp group_and_subkey_need_reboot?(group, key, value) do
|
||||
Keyword.keyword?(value) and
|
||||
Enum.any?(@reboot_time_subkeys, fn {g, k, subkeys} ->
|
||||
g == group and k == key and
|
||||
Enum.any?(Keyword.keys(value), &(&1 in subkeys))
|
||||
end)
|
||||
end
|
||||
|
||||
defp restart(_, :pleroma, env), do: Restarter.Pleroma.restart_after_boot(env)
|
||||
|
||||
defp restart(started_applications, app, _) do
|
||||
with {^app, _, _} <- List.keyfind(started_applications, app, 0),
|
||||
:ok <- Application.stop(app) do
|
||||
:ok = Application.start(app)
|
||||
else
|
||||
nil ->
|
||||
Logger.warn("#{app} is not started.")
|
||||
|
||||
error ->
|
||||
error
|
||||
|> inspect()
|
||||
|> Logger.warn()
|
||||
end
|
||||
end
|
||||
|
||||
defp can_be_merged?(val1, val2) when is_list(val1) and is_list(val2) do
|
||||
Keyword.keyword?(val1) and Keyword.keyword?(val2)
|
||||
end
|
||||
|
||||
defp can_be_merged?(_val1, _val2), do: false
|
||||
end
|
25
lib/pleroma/config/version.ex
Normal file
25
lib/pleroma/config/version.ex
Normal file
@ -0,0 +1,25 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Config.Version do
|
||||
@moduledoc """
|
||||
IMPORTANT!!!
|
||||
Before modifying records in the database directly, please read "Config versioning" in `docs/development/config_versioning.md`.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Query, only: [from: 2]
|
||||
|
||||
schema "config_versions" do
|
||||
field(:backup, Pleroma.EctoType.Config.BinaryValue)
|
||||
field(:current, :boolean, default: true)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def all do
|
||||
from(v in __MODULE__, order_by: [desc: v.id]) |> Pleroma.Repo.all()
|
||||
end
|
||||
end
|
292
lib/pleroma/config/versioning.ex
Normal file
292
lib/pleroma/config/versioning.ex
Normal file
@ -0,0 +1,292 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Config.Versioning do
|
||||
@moduledoc """
|
||||
Module that manages versions of database configs.
|
||||
"""
|
||||
|
||||
import Ecto.Query, only: [from: 2]
|
||||
|
||||
alias Ecto.Multi
|
||||
alias Pleroma.Config.Version
|
||||
alias Pleroma.ConfigDB
|
||||
alias Pleroma.Repo
|
||||
|
||||
@type change :: %{
|
||||
optional(:delete) => boolean(),
|
||||
optional(:value) => any(),
|
||||
group: atom(),
|
||||
key: atom() | nil
|
||||
}
|
||||
|
||||
@doc """
|
||||
Creates new config version:
|
||||
- convert changes to elixir types
|
||||
- splits changes by type and processes them in `config` table
|
||||
- sets all pointers to false
|
||||
- gets all rows from `config` table and inserts them as keyword in `backup` field
|
||||
"""
|
||||
@spec new_version([change()] | change()) ::
|
||||
{:ok, map()} | {:error, :no_changes} | {:error, atom() | tuple(), any(), any()}
|
||||
def new_version([]), do: {:error, :empty_changes}
|
||||
def new_version(change) when is_map(change), do: new_version([change])
|
||||
|
||||
def new_version(changes) when is_list(changes) do
|
||||
changes
|
||||
|> Enum.reduce(Multi.new(), fn
|
||||
%{delete: true} = deletion, acc ->
|
||||
Multi.run(acc, {:delete_or_update, deletion[:group], deletion[:key]}, fn _, _ ->
|
||||
ConfigDB.delete_or_update(deletion)
|
||||
end)
|
||||
|
||||
operation, acc ->
|
||||
{name, fun} =
|
||||
if Keyword.keyword?(operation[:value]) or
|
||||
(operation[:group] == :pleroma and
|
||||
operation[:key] in ConfigDB.pleroma_not_keyword_values()) do
|
||||
{:insert_or_update,
|
||||
fn _, _ ->
|
||||
ConfigDB.update_or_create(operation)
|
||||
end}
|
||||
else
|
||||
{:error,
|
||||
fn _, _ ->
|
||||
{:error, {:value_must_be_keyword, operation}}
|
||||
end}
|
||||
end
|
||||
|
||||
Multi.run(acc, {name, operation[:group], operation[:key]}, fun)
|
||||
end)
|
||||
|> set_current_flag_false_for_all_versions()
|
||||
|> insert_new_version()
|
||||
|> Repo.transaction()
|
||||
end
|
||||
|
||||
def new_version(_), do: {:error, :bad_format}
|
||||
|
||||
defp set_current_flag_false_for_all_versions(multi) do
|
||||
Multi.update_all(multi, :update_all_versions, Version, set: [current: false])
|
||||
end
|
||||
|
||||
defp insert_new_version(multi) do
|
||||
Multi.run(multi, :insert_version, fn repo, _ ->
|
||||
%Version{
|
||||
backup: ConfigDB.all_as_keyword()
|
||||
}
|
||||
|> repo.insert()
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Rollbacks config version by N steps:
|
||||
- checks possibility for rollback
|
||||
- truncates config table and restarts pk
|
||||
- inserts config settings from backup
|
||||
- sets all pointers to false
|
||||
- sets current pointer to true for rollback version
|
||||
- deletes versions after current
|
||||
"""
|
||||
@spec rollback(pos_integer()) ::
|
||||
{:ok, map()}
|
||||
| {:error, atom() | tuple(), any(), any()}
|
||||
| {:error, :steps_format}
|
||||
| {:error, :no_current_version}
|
||||
| {:error, :rollback_not_possible}
|
||||
def rollback(steps \\ 1)
|
||||
|
||||
def rollback(steps) when is_integer(steps) and steps > 0 do
|
||||
with version_id when is_integer(version_id) <- get_current_version_id(),
|
||||
%Version{} = version <- get_version_by_steps(steps) do
|
||||
do_rollback(version)
|
||||
end
|
||||
end
|
||||
|
||||
def rollback(_), do: {:error, :steps_format}
|
||||
|
||||
@doc """
|
||||
Same as `rollback/1`, but rollbacks for a given version id.
|
||||
"""
|
||||
@spec rollback_by_id(pos_integer()) ::
|
||||
{:ok, map()}
|
||||
| {:error, atom() | tuple(), any(), any()}
|
||||
| {:error, :not_found}
|
||||
| {:error, :version_is_already_current}
|
||||
def rollback_by_id(id) when is_integer(id) do
|
||||
with %Version{current: false} = version <- get_version_by_id(id) do
|
||||
do_rollback(version)
|
||||
else
|
||||
%Version{current: true} -> {:error, :version_is_already_current}
|
||||
error -> error
|
||||
end
|
||||
end
|
||||
|
||||
defp get_current_version_id do
|
||||
query = from(v in Version, where: v.current == true)
|
||||
|
||||
with nil <- Repo.aggregate(query, :max, :id) do
|
||||
{:error, :no_current_version}
|
||||
end
|
||||
end
|
||||
|
||||
defp get_version_by_id(id) do
|
||||
with nil <- Repo.get(Version, id) do
|
||||
{:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
defp get_version_by_steps(steps) do
|
||||
query = from(v in Version, order_by: [desc: v.id], limit: 1, offset: ^steps)
|
||||
|
||||
with nil <- Repo.one(query) do
|
||||
{:error, :rollback_not_possible}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_rollback(version) do
|
||||
multi =
|
||||
truncate_config_table()
|
||||
|> reset_pk_in_config_table()
|
||||
|
||||
version.backup
|
||||
|> ConfigDB.from_keyword_to_maps()
|
||||
|> add_insert_commands(multi)
|
||||
|> set_current_flag_false_for_all_versions()
|
||||
|> Multi.update(:move_current_pointer, Ecto.Changeset.change(version, current: true))
|
||||
|> Multi.delete_all(
|
||||
:delete_next_versions,
|
||||
from(v in Version, where: v.id > ^version.id)
|
||||
)
|
||||
|> Repo.transaction()
|
||||
end
|
||||
|
||||
defp truncate_config_table(multi \\ Multi.new()) do
|
||||
Multi.run(multi, :truncate_config_table, fn repo, _ ->
|
||||
repo.query("TRUNCATE config;")
|
||||
end)
|
||||
end
|
||||
|
||||
defp reset_pk_in_config_table(multi) do
|
||||
Multi.run(multi, :reset_pk, fn repo, _ ->
|
||||
repo.query("ALTER SEQUENCE config_id_seq RESTART;")
|
||||
end)
|
||||
end
|
||||
|
||||
defp add_insert_commands(changes, multi) do
|
||||
Enum.reduce(changes, multi, fn change, acc ->
|
||||
Multi.run(acc, {:insert, change[:group], change[:key]}, fn _, _ ->
|
||||
ConfigDB.update_or_create(change)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Resets config table and creates new empty version.
|
||||
"""
|
||||
@spec reset() :: {:ok, map()} | {:error, atom() | tuple(), any(), any()}
|
||||
def reset do
|
||||
truncate_config_table()
|
||||
|> reset_pk_in_config_table()
|
||||
|> set_current_flag_false_for_all_versions()
|
||||
|> insert_new_version()
|
||||
|> Repo.transaction()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Migrates settings from config file into database:
|
||||
- truncates config table and restarts pk
|
||||
- inserts settings from config file
|
||||
- sets all pointers to false
|
||||
- gets all rows from `config` table and inserts them as keyword in `backup` field
|
||||
"""
|
||||
@spec migrate(Path.t()) :: {:ok, map()} | {:error, atom() | tuple(), any(), any()}
|
||||
def migrate(config_path) do
|
||||
multi =
|
||||
truncate_config_table()
|
||||
|> reset_pk_in_config_table()
|
||||
|
||||
config_path
|
||||
|> Pleroma.Config.Loader.read!()
|
||||
|> Pleroma.Config.Loader.filter()
|
||||
|> ConfigDB.from_keyword_to_maps()
|
||||
|> add_insert_commands(multi)
|
||||
|> set_current_flag_false_for_all_versions()
|
||||
|> insert_new_version()
|
||||
|> Repo.transaction()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Common function to migrate old config namespace to the new one keeping the old value.
|
||||
"""
|
||||
@spec migrate_namespace({atom(), atom()}, {atom(), atom()}) ::
|
||||
{:ok, map()} | {:error, atom() | tuple(), any(), any()}
|
||||
def migrate_namespace({o_group, o_key}, {n_group, n_key}) do
|
||||
config = ConfigDB.get_by_params(%{group: o_group, key: o_key})
|
||||
|
||||
configs_changes_fun =
|
||||
if config do
|
||||
fn ->
|
||||
config
|
||||
|> Ecto.Changeset.change(group: n_group, key: n_key)
|
||||
|> Repo.update()
|
||||
end
|
||||
else
|
||||
fn -> {:ok, nil} end
|
||||
end
|
||||
|
||||
versions_changes_fun = fn %{backup: backup} = version ->
|
||||
with {value, rest} when not is_nil(value) <- pop_in(backup[o_group][o_key]) do
|
||||
rest =
|
||||
if rest[o_group] == [] do
|
||||
Keyword.delete(rest, o_group)
|
||||
else
|
||||
rest
|
||||
end
|
||||
|
||||
updated_backup =
|
||||
if Keyword.has_key?(rest, n_group) do
|
||||
put_in(rest[n_group][n_key], value)
|
||||
else
|
||||
Keyword.put(rest, n_group, [{n_key, value}])
|
||||
end
|
||||
|
||||
version
|
||||
|> Ecto.Changeset.change(backup: updated_backup)
|
||||
|> Repo.update()
|
||||
else
|
||||
_ -> {:ok, nil}
|
||||
end
|
||||
end
|
||||
|
||||
migrate_configs_and_versions(configs_changes_fun, versions_changes_fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Abstract function for config migrations to keep changes in config table and changes in versions backups in transaction.
|
||||
Accepts two functions:
|
||||
- first function makes changes to the configs
|
||||
- second function makes changes to the backups in versions
|
||||
"""
|
||||
@spec migrate_configs_and_versions(function(), function()) ::
|
||||
{:ok, map()} | {:error, atom() | tuple(), any(), any()}
|
||||
def migrate_configs_and_versions(configs_changes_fun, version_change_fun)
|
||||
when is_function(configs_changes_fun, 0) and
|
||||
is_function(version_change_fun, 1) do
|
||||
versions = Repo.all(Version)
|
||||
|
||||
multi =
|
||||
Multi.new()
|
||||
|> Multi.run(:configs_changes, fn _, _ ->
|
||||
configs_changes_fun.()
|
||||
end)
|
||||
|
||||
versions
|
||||
|> Enum.reduce(multi, fn version, acc ->
|
||||
Multi.run(acc, {:version_change, version.id}, fn _, _ ->
|
||||
version_change_fun.(version)
|
||||
end)
|
||||
end)
|
||||
|> Repo.transaction()
|
||||
end
|
||||
end
|
@ -6,8 +6,7 @@ defmodule Pleroma.ConfigDB do
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
import Ecto.Query, only: [select: 3, from: 2]
|
||||
import Pleroma.Web.Gettext
|
||||
import Ecto.Query, only: [from: 2]
|
||||
|
||||
alias __MODULE__
|
||||
alias Pleroma.Repo
|
||||
@ -22,6 +21,10 @@ defmodule Pleroma.ConfigDB do
|
||||
{:pleroma, :mrf_keyword, :replace}
|
||||
]
|
||||
|
||||
@groups_without_keys [:quack, :mime, :cors_plug, :esshd, :ex_aws, :joken, :logger, :swoosh]
|
||||
|
||||
@pleroma_not_keyword_values [Pleroma.Web.Auth.Authenticator, :admin_token]
|
||||
|
||||
schema "config" do
|
||||
field(:key, Pleroma.EctoType.Config.Atom)
|
||||
field(:group, Pleroma.EctoType.Config.Atom)
|
||||
@ -31,13 +34,35 @@ defmodule Pleroma.ConfigDB do
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@spec get_all_as_keyword() :: keyword()
|
||||
def get_all_as_keyword do
|
||||
ConfigDB
|
||||
|> select([c], {c.group, c.key, c.value})
|
||||
|> Repo.all()
|
||||
|> Enum.reduce([], fn {group, key, value}, acc ->
|
||||
Keyword.update(acc, group, [{key, value}], &Keyword.merge(&1, [{key, value}]))
|
||||
@spec all() :: [t()]
|
||||
def all, do: Repo.all(ConfigDB)
|
||||
|
||||
@spec all_with_db() :: [t()]
|
||||
def all_with_db do
|
||||
all()
|
||||
|> Enum.map(fn
|
||||
%{group: :pleroma, key: key} = change when key in @pleroma_not_keyword_values ->
|
||||
%{change | db: [change.key]}
|
||||
|
||||
%{value: value} = change ->
|
||||
%{change | db: Keyword.keys(value)}
|
||||
end)
|
||||
end
|
||||
|
||||
@spec all_as_keyword() :: keyword()
|
||||
def all_as_keyword do
|
||||
all()
|
||||
|> as_keyword()
|
||||
end
|
||||
|
||||
@spec as_keyword([t()]) :: keyword()
|
||||
def as_keyword(changes) do
|
||||
Enum.reduce(changes, [], fn
|
||||
%{group: group, key: nil, value: value}, acc ->
|
||||
Keyword.update(acc, group, value, &Keyword.merge(&1, value))
|
||||
|
||||
%{group: group, key: key, value: value}, acc ->
|
||||
Keyword.update(acc, group, [{key, value}], &Keyword.merge(&1, [{key, value}]))
|
||||
end)
|
||||
end
|
||||
|
||||
@ -52,14 +77,22 @@ defmodule Pleroma.ConfigDB do
|
||||
end
|
||||
|
||||
@spec get_by_params(map()) :: ConfigDB.t() | nil
|
||||
def get_by_params(%{group: _, key: _} = params), do: Repo.get_by(ConfigDB, params)
|
||||
def get_by_params(%{group: group, key: key} = params)
|
||||
when not is_nil(key) and not is_nil(group) do
|
||||
Repo.get_by(ConfigDB, params)
|
||||
end
|
||||
|
||||
def get_by_params(%{group: group}) do
|
||||
from(c in ConfigDB, where: c.group == ^group and is_nil(c.key)) |> Repo.one()
|
||||
end
|
||||
|
||||
@spec changeset(ConfigDB.t(), map()) :: Changeset.t()
|
||||
def changeset(config, params \\ %{}) do
|
||||
config
|
||||
|> cast(params, [:key, :group, :value])
|
||||
|> validate_required([:key, :group, :value])
|
||||
|> validate_required([:group, :value])
|
||||
|> unique_constraint(:key, name: :config_group_key_index)
|
||||
|> unique_constraint(:key, name: :config_group__key_is_null_index)
|
||||
end
|
||||
|
||||
defp create(params) do
|
||||
@ -74,53 +107,104 @@ defmodule Pleroma.ConfigDB do
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@spec get_db_keys(keyword(), any()) :: [String.t()]
|
||||
def get_db_keys(value, key) do
|
||||
keys =
|
||||
if Keyword.keyword?(value) do
|
||||
Keyword.keys(value)
|
||||
else
|
||||
[key]
|
||||
end
|
||||
@doc """
|
||||
IMPORTANT!!!
|
||||
Before modifying records in the database directly, please read "Config versioning" in `docs/development/config_versioning.md`.
|
||||
"""
|
||||
@spec update_or_create(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
|
||||
def update_or_create(params) do
|
||||
search_opts = Map.take(params, [:group, :key])
|
||||
|
||||
Enum.map(keys, &to_json_types(&1))
|
||||
with %ConfigDB{} = config <- ConfigDB.get_by_params(search_opts) do
|
||||
new_value = merge_group(config.group, config.key, config.value, params[:value])
|
||||
|
||||
update(config, %{value: new_value})
|
||||
else
|
||||
nil ->
|
||||
create(params)
|
||||
end
|
||||
end
|
||||
|
||||
@spec merge_group(atom(), atom(), keyword(), keyword()) :: keyword()
|
||||
def merge_group(group, key, old_value, new_value) do
|
||||
@doc """
|
||||
IMPORTANT!!!
|
||||
Before modifying records in the database directly, please read "Config versioning" in `docs/development/config_versioning.md`.
|
||||
"""
|
||||
@spec delete(ConfigDB.t() | map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
|
||||
def delete(%ConfigDB{} = config), do: Repo.delete(config)
|
||||
|
||||
@doc """
|
||||
IMPORTANT!!!
|
||||
Before modifying records in the database directly, please read "Config versioning" in `docs/development/config_versioning.md`.
|
||||
"""
|
||||
@spec delete_or_update(map()) :: {:ok, t()} | {:ok, nil} | {:error, Changeset.t()}
|
||||
def delete_or_update(%{group: _, key: key} = params) when not is_nil(key) do
|
||||
search_opts = Map.take(params, [:group, :key])
|
||||
|
||||
with %ConfigDB{} = config <- ConfigDB.get_by_params(search_opts) do
|
||||
do_delete_or_update(config, params[:subkeys])
|
||||
else
|
||||
_ -> {:ok, nil}
|
||||
end
|
||||
end
|
||||
|
||||
def delete_or_update(%{group: group}) do
|
||||
query = from(c in ConfigDB, where: c.group == ^group)
|
||||
|
||||
with {num, _} <- Repo.delete_all(query) do
|
||||
{:ok, num}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_delete_or_update(%ConfigDB{} = config, subkeys)
|
||||
when is_list(subkeys) and subkeys != [] do
|
||||
new_value = Keyword.drop(config.value, subkeys)
|
||||
|
||||
if new_value == [] do
|
||||
delete(config)
|
||||
else
|
||||
update(config, %{value: new_value})
|
||||
end
|
||||
end
|
||||
|
||||
defp do_delete_or_update(%ConfigDB{} = config, _), do: delete(config)
|
||||
|
||||
defp merge_group(group, key, old_value, new_value)
|
||||
when is_list(old_value) and is_list(new_value) do
|
||||
new_keys = to_mapset(new_value)
|
||||
|
||||
intersect_keys = old_value |> to_mapset() |> MapSet.intersection(new_keys) |> MapSet.to_list()
|
||||
|
||||
merged_value = ConfigDB.merge(old_value, new_value)
|
||||
merged_value = deep_merge(old_value, new_value)
|
||||
|
||||
@full_subkey_update
|
||||
|> Enum.map(fn
|
||||
{g, k, subkey} when g == group and k == key ->
|
||||
if subkey in intersect_keys, do: subkey, else: []
|
||||
|> Enum.reduce([], fn
|
||||
{g, k, subkey}, acc when g == group and k == key ->
|
||||
if subkey in intersect_keys do
|
||||
[subkey | acc]
|
||||
else
|
||||
acc
|
||||
end
|
||||
|
||||
_ ->
|
||||
[]
|
||||
_, acc ->
|
||||
acc
|
||||
end)
|
||||
|> List.flatten()
|
||||
|> Enum.reduce(merged_value, &Keyword.put(&2, &1, new_value[&1]))
|
||||
end
|
||||
|
||||
defp to_mapset(keyword) do
|
||||
defp merge_group(_group, _key, _old_value, new_value) when is_list(new_value), do: new_value
|
||||
|
||||
defp merge_group(:pleroma, key, _old_value, new_value)
|
||||
when key in @pleroma_not_keyword_values do
|
||||
new_value
|
||||
end
|
||||
|
||||
defp to_mapset(keyword) when is_list(keyword) do
|
||||
keyword
|
||||
|> Keyword.keys()
|
||||
|> MapSet.new()
|
||||
end
|
||||
|
||||
@spec sub_key_full_update?(atom(), atom(), [Keyword.key()]) :: boolean()
|
||||
def sub_key_full_update?(group, key, subkeys) do
|
||||
Enum.any?(@full_subkey_update, fn {g, k, subkey} ->
|
||||
g == group and k == key and subkey in subkeys
|
||||
end)
|
||||
end
|
||||
|
||||
@spec merge(keyword(), keyword()) :: keyword()
|
||||
def merge(config1, config2) when is_list(config1) and is_list(config2) do
|
||||
defp deep_merge(config1, config2) when is_list(config1) and is_list(config2) do
|
||||
Keyword.merge(config1, config2, fn _, app1, app2 ->
|
||||
if Keyword.keyword?(app1) and Keyword.keyword?(app2) do
|
||||
Keyword.merge(app1, app2, &deep_merge/3)
|
||||
@ -138,255 +222,99 @@ defmodule Pleroma.ConfigDB do
|
||||
end
|
||||
end
|
||||
|
||||
@spec update_or_create(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
|
||||
def update_or_create(params) do
|
||||
params = Map.put(params, :value, to_elixir_types(params[:value]))
|
||||
search_opts = Map.take(params, [:group, :key])
|
||||
@spec reduce_defaults_and_merge_with_changes([t()], keyword()) :: {[t()], keyword()}
|
||||
def reduce_defaults_and_merge_with_changes(changes, defaults) do
|
||||
Enum.reduce(changes, {[], defaults}, &reduce_default_and_merge_with_change/2)
|
||||
end
|
||||
|
||||
with %ConfigDB{} = config <- ConfigDB.get_by_params(search_opts),
|
||||
{_, true, config} <- {:partial_update, can_be_partially_updated?(config), config},
|
||||
{_, true, config} <-
|
||||
{:can_be_merged, is_list(params[:value]) and is_list(config.value), config} do
|
||||
new_value = merge_group(config.group, config.key, config.value, params[:value])
|
||||
update(config, %{value: new_value})
|
||||
defp reduce_default_and_merge_with_change(%{group: group} = change, {acc, defaults})
|
||||
when group in @groups_without_keys do
|
||||
{default, remaining_defaults} = Keyword.pop(defaults, group)
|
||||
|
||||
change = merge_change_with_default(change, default)
|
||||
{[change | acc], remaining_defaults}
|
||||
end
|
||||
|
||||
defp reduce_default_and_merge_with_change(%{group: group, key: key} = change, {acc, defaults}) do
|
||||
if defaults[group] do
|
||||
{default, remaining_group_defaults} = Keyword.pop(defaults[group], key)
|
||||
|
||||
remaining_defaults =
|
||||
if remaining_group_defaults == [] do
|
||||
Keyword.delete(defaults, group)
|
||||
else
|
||||
Keyword.put(defaults, group, remaining_group_defaults)
|
||||
end
|
||||
|
||||
change = merge_change_with_default(change, default)
|
||||
|
||||
{[change | acc], remaining_defaults}
|
||||
else
|
||||
{reason, false, config} when reason in [:partial_update, :can_be_merged] ->
|
||||
update(config, params)
|
||||
|
||||
nil ->
|
||||
create(params)
|
||||
{[change | acc], defaults}
|
||||
end
|
||||
end
|
||||
|
||||
defp can_be_partially_updated?(%ConfigDB{} = config), do: not only_full_update?(config)
|
||||
@spec from_keyword_to_structs(keyword(), [] | [t()]) :: [t()]
|
||||
def from_keyword_to_structs(keyword, initial_acc \\ []) do
|
||||
Enum.reduce(keyword, initial_acc, &reduce_to_structs/2)
|
||||
end
|
||||
|
||||
defp only_full_update?(%ConfigDB{group: group, key: key}) do
|
||||
full_key_update = [
|
||||
{:pleroma, :ecto_repos},
|
||||
{:quack, :meta},
|
||||
{:mime, :types},
|
||||
{:cors_plug, [:max_age, :methods, :expose, :headers]},
|
||||
{:swarm, :node_blacklist},
|
||||
{:logger, :backends}
|
||||
]
|
||||
defp reduce_to_structs({group, config}, group_acc) when group in @groups_without_keys do
|
||||
[struct(%ConfigDB{}, to_map(group, config)) | group_acc]
|
||||
end
|
||||
|
||||
Enum.any?(full_key_update, fn
|
||||
{s_group, s_key} ->
|
||||
group == s_group and ((is_list(s_key) and key in s_key) or key == s_key)
|
||||
defp reduce_to_structs({group, config}, group_acc) do
|
||||
Enum.reduce(config, group_acc, fn {key, value}, acc ->
|
||||
[struct(%ConfigDB{}, to_map(group, key, value)) | acc]
|
||||
end)
|
||||
end
|
||||
|
||||
@spec delete(ConfigDB.t() | map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
|
||||
def delete(%ConfigDB{} = config), do: Repo.delete(config)
|
||||
@spec from_keyword_to_maps(keyword(), [] | [map()]) :: [map()]
|
||||
def from_keyword_to_maps(keyword, initial_acc \\ []) do
|
||||
Enum.reduce(keyword, initial_acc, &reduce_to_maps/2)
|
||||
end
|
||||
|
||||
def delete(params) do
|
||||
search_opts = Map.delete(params, :subkeys)
|
||||
defp reduce_to_maps({group, config}, group_acc) when group in @groups_without_keys do
|
||||
[to_map(group, config) | group_acc]
|
||||
end
|
||||
|
||||
with %ConfigDB{} = config <- ConfigDB.get_by_params(search_opts),
|
||||
{config, sub_keys} when is_list(sub_keys) <- {config, params[:subkeys]},
|
||||
keys <- Enum.map(sub_keys, &string_to_elixir_types(&1)),
|
||||
{_, config, new_value} when new_value != [] <-
|
||||
{:partial_remove, config, Keyword.drop(config.value, keys)} do
|
||||
update(config, %{value: new_value})
|
||||
defp reduce_to_maps({group, config}, group_acc) do
|
||||
Enum.reduce(config, group_acc, fn {key, value}, acc ->
|
||||
[to_map(group, key, value) | acc]
|
||||
end)
|
||||
end
|
||||
|
||||
defp to_map(group, config), do: %{group: group, value: config}
|
||||
|
||||
defp to_map(group, key, value), do: %{group: group, key: key, value: value}
|
||||
|
||||
@spec merge_changes_with_defaults([t()], keyword()) :: [t()]
|
||||
def merge_changes_with_defaults(changes, defaults) when is_list(changes) do
|
||||
Enum.map(changes, fn
|
||||
%{group: group} = change when group in @groups_without_keys ->
|
||||
merge_change_with_default(change, defaults[group])
|
||||
|
||||
%{group: group, key: key} = change ->
|
||||
merge_change_with_default(change, defaults[group][key])
|
||||
end)
|
||||
end
|
||||
|
||||
defp merge_change_with_default(change, default) do
|
||||
%{change | value: merge_change_value_with_default(change, default)}
|
||||
end
|
||||
|
||||
@spec merge_change_value_with_default(t(), keyword()) :: keyword()
|
||||
def merge_change_value_with_default(change, default) do
|
||||
if Ecto.get_meta(change, :state) == :deleted do
|
||||
default
|
||||
else
|
||||
{:partial_remove, config, []} ->
|
||||
Repo.delete(config)
|
||||
|
||||
{config, nil} ->
|
||||
Repo.delete(config)
|
||||
|
||||
nil ->
|
||||
err =
|
||||
dgettext("errors", "Config with params %{params} not found", params: inspect(params))
|
||||
|
||||
{:error, err}
|
||||
merge_group(change.group, change.key, default, change.value)
|
||||
end
|
||||
end
|
||||
|
||||
@spec to_json_types(term()) :: map() | list() | boolean() | String.t()
|
||||
def to_json_types(entity) when is_list(entity) do
|
||||
Enum.map(entity, &to_json_types/1)
|
||||
end
|
||||
@spec groups_without_keys() :: [atom()]
|
||||
def groups_without_keys, do: @groups_without_keys
|
||||
|
||||
def to_json_types(%Regex{} = entity), do: inspect(entity)
|
||||
|
||||
def to_json_types(entity) when is_map(entity) do
|
||||
Map.new(entity, fn {k, v} -> {to_json_types(k), to_json_types(v)} end)
|
||||
end
|
||||
|
||||
def to_json_types({:args, args}) when is_list(args) do
|
||||
arguments =
|
||||
Enum.map(args, fn
|
||||
arg when is_tuple(arg) -> inspect(arg)
|
||||
arg -> to_json_types(arg)
|
||||
end)
|
||||
|
||||
%{"tuple" => [":args", arguments]}
|
||||
end
|
||||
|
||||
def to_json_types({:proxy_url, {type, :localhost, port}}) do
|
||||
%{"tuple" => [":proxy_url", %{"tuple" => [to_json_types(type), "localhost", port]}]}
|
||||
end
|
||||
|
||||
def to_json_types({:proxy_url, {type, host, port}}) when is_tuple(host) do
|
||||
ip =
|
||||
host
|
||||
|> :inet_parse.ntoa()
|
||||
|> to_string()
|
||||
|
||||
%{
|
||||
"tuple" => [
|
||||
":proxy_url",
|
||||
%{"tuple" => [to_json_types(type), ip, port]}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def to_json_types({:proxy_url, {type, host, port}}) do
|
||||
%{
|
||||
"tuple" => [
|
||||
":proxy_url",
|
||||
%{"tuple" => [to_json_types(type), to_string(host), port]}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def to_json_types({:partial_chain, entity}),
|
||||
do: %{"tuple" => [":partial_chain", inspect(entity)]}
|
||||
|
||||
def to_json_types(entity) when is_tuple(entity) do
|
||||
value =
|
||||
entity
|
||||
|> Tuple.to_list()
|
||||
|> to_json_types()
|
||||
|
||||
%{"tuple" => value}
|
||||
end
|
||||
|
||||
def to_json_types(entity) when is_binary(entity), do: entity
|
||||
|
||||
def to_json_types(entity) when is_boolean(entity) or is_number(entity) or is_nil(entity) do
|
||||
entity
|
||||
end
|
||||
|
||||
def to_json_types(entity) when entity in [:"tlsv1.1", :"tlsv1.2", :"tlsv1.3"] do
|
||||
":#{entity}"
|
||||
end
|
||||
|
||||
def to_json_types(entity) when is_atom(entity), do: inspect(entity)
|
||||
|
||||
@spec to_elixir_types(boolean() | String.t() | map() | list()) :: term()
|
||||
def to_elixir_types(%{"tuple" => [":args", args]}) when is_list(args) do
|
||||
arguments =
|
||||
Enum.map(args, fn arg ->
|
||||
if String.contains?(arg, ["{", "}"]) do
|
||||
{elem, []} = Code.eval_string(arg)
|
||||
elem
|
||||
else
|
||||
to_elixir_types(arg)
|
||||
end
|
||||
end)
|
||||
|
||||
{:args, arguments}
|
||||
end
|
||||
|
||||
def to_elixir_types(%{"tuple" => [":proxy_url", %{"tuple" => [type, host, port]}]}) do
|
||||
{:proxy_url, {string_to_elixir_types(type), parse_host(host), port}}
|
||||
end
|
||||
|
||||
def to_elixir_types(%{"tuple" => [":partial_chain", entity]}) do
|
||||
{partial_chain, []} =
|
||||
entity
|
||||
|> String.replace(~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "")
|
||||
|> Code.eval_string()
|
||||
|
||||
{:partial_chain, partial_chain}
|
||||
end
|
||||
|
||||
def to_elixir_types(%{"tuple" => entity}) do
|
||||
Enum.reduce(entity, {}, &Tuple.append(&2, to_elixir_types(&1)))
|
||||
end
|
||||
|
||||
def to_elixir_types(entity) when is_map(entity) do
|
||||
Map.new(entity, fn {k, v} -> {to_elixir_types(k), to_elixir_types(v)} end)
|
||||
end
|
||||
|
||||
def to_elixir_types(entity) when is_list(entity) do
|
||||
Enum.map(entity, &to_elixir_types/1)
|
||||
end
|
||||
|
||||
def to_elixir_types(entity) when is_binary(entity) do
|
||||
entity
|
||||
|> String.trim()
|
||||
|> string_to_elixir_types()
|
||||
end
|
||||
|
||||
def to_elixir_types(entity), do: entity
|
||||
|
||||
@spec string_to_elixir_types(String.t()) ::
|
||||
atom() | Regex.t() | module() | String.t() | no_return()
|
||||
def string_to_elixir_types("~r" <> _pattern = regex) do
|
||||
pattern =
|
||||
~r/^~r(?'delimiter'[\/|"'([{<]{1})(?'pattern'.+)[\/|"')\]}>]{1}(?'modifier'[uismxfU]*)/u
|
||||
|
||||
delimiters = ["/", "|", "\"", "'", {"(", ")"}, {"[", "]"}, {"{", "}"}, {"<", ">"}]
|
||||
|
||||
with %{"modifier" => modifier, "pattern" => pattern, "delimiter" => regex_delimiter} <-
|
||||
Regex.named_captures(pattern, regex),
|
||||
{:ok, {leading, closing}} <- find_valid_delimiter(delimiters, pattern, regex_delimiter),
|
||||
{result, _} <- Code.eval_string("~r#{leading}#{pattern}#{closing}#{modifier}") do
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
def string_to_elixir_types(":" <> atom), do: String.to_atom(atom)
|
||||
|
||||
def string_to_elixir_types(value) do
|
||||
if module_name?(value) do
|
||||
String.to_existing_atom("Elixir." <> value)
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_host("localhost"), do: :localhost
|
||||
|
||||
defp parse_host(host) do
|
||||
charlist = to_charlist(host)
|
||||
|
||||
case :inet.parse_address(charlist) do
|
||||
{:error, :einval} ->
|
||||
charlist
|
||||
|
||||
{:ok, ip} ->
|
||||
ip
|
||||
end
|
||||
end
|
||||
|
||||
defp find_valid_delimiter([], _string, _) do
|
||||
raise(ArgumentError, message: "valid delimiter for Regex expression not found")
|
||||
end
|
||||
|
||||
defp find_valid_delimiter([{leading, closing} = delimiter | others], pattern, regex_delimiter)
|
||||
when is_tuple(delimiter) do
|
||||
if String.contains?(pattern, closing) do
|
||||
find_valid_delimiter(others, pattern, regex_delimiter)
|
||||
else
|
||||
{:ok, {leading, closing}}
|
||||
end
|
||||
end
|
||||
|
||||
defp find_valid_delimiter([delimiter | others], pattern, regex_delimiter) do
|
||||
if String.contains?(pattern, delimiter) do
|
||||
find_valid_delimiter(others, pattern, regex_delimiter)
|
||||
else
|
||||
{:ok, {delimiter, delimiter}}
|
||||
end
|
||||
end
|
||||
|
||||
@spec module_name?(String.t()) :: boolean()
|
||||
def module_name?(string) do
|
||||
Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack|Ueberauth|Swoosh)\./, string) or
|
||||
string in ["Oban", "Ueberauth", "ExSyslogger"]
|
||||
end
|
||||
@spec pleroma_not_keyword_values() :: [atom()]
|
||||
def pleroma_not_keyword_values, do: @pleroma_not_keyword_values
|
||||
end
|
||||
|
@ -5,7 +5,7 @@
|
||||
defmodule Pleroma.Docs.JSON do
|
||||
@behaviour Pleroma.Docs.Generator
|
||||
@external_resource "config/description.exs"
|
||||
@raw_config Pleroma.Config.Loader.read("config/description.exs")
|
||||
@raw_config Pleroma.Config.Loader.read!("config/description.exs")
|
||||
@raw_descriptions @raw_config[:pleroma][:config_description]
|
||||
@term __MODULE__.Compiled
|
||||
|
||||
|
@ -12,13 +12,13 @@ defmodule Pleroma.EctoType.Config.Atom do
|
||||
end
|
||||
|
||||
def cast(key) when is_binary(key) do
|
||||
{:ok, Pleroma.ConfigDB.string_to_elixir_types(key)}
|
||||
{:ok, Pleroma.Config.Converter.string_to_elixir_types!(key)}
|
||||
end
|
||||
|
||||
def cast(_), do: :error
|
||||
|
||||
def load(key) do
|
||||
{:ok, Pleroma.ConfigDB.string_to_elixir_types(key)}
|
||||
{:ok, Pleroma.Config.Converter.string_to_elixir_types!(key)}
|
||||
end
|
||||
|
||||
def dump(key) when is_atom(key), do: {:ok, inspect(key)}
|
||||
|
@ -15,6 +15,10 @@ defmodule Pleroma.EctoType.Config.BinaryValue do
|
||||
end
|
||||
end
|
||||
|
||||
def cast(value) when is_map(value) or is_list(value) do
|
||||
{:ok, Pleroma.Config.Converter.to_elixir_types(value)}
|
||||
end
|
||||
|
||||
def cast(value), do: {:ok, value}
|
||||
|
||||
def load(value) when is_binary(value) do
|
||||
|
@ -8,12 +8,11 @@ defmodule Pleroma.Emails.UserEmail do
|
||||
use Phoenix.Swoosh, view: Pleroma.Web.EmailView, layout: {Pleroma.Web.LayoutView, :email}
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Helpers.ConfigHelper
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Pleroma.Web.Router
|
||||
|
||||
import Pleroma.Config.Helpers, only: [instance_name: 0, sender: 0]
|
||||
|
||||
defp recipient(email, nil), do: email
|
||||
defp recipient(email, name), do: {name, email}
|
||||
defp recipient(%User{} = user), do: recipient(user.email, user.name)
|
||||
@ -22,25 +21,25 @@ defmodule Pleroma.Emails.UserEmail do
|
||||
def welcome(user, opts \\ %{}) do
|
||||
new()
|
||||
|> to(recipient(user))
|
||||
|> from(Map.get(opts, :sender, sender()))
|
||||
|> subject(Map.get(opts, :subject, "Welcome to #{instance_name()}!"))
|
||||
|> html_body(Map.get(opts, :html, "Welcome to #{instance_name()}!"))
|
||||
|> text_body(Map.get(opts, :text, "Welcome to #{instance_name()}!"))
|
||||
|> from(Map.get(opts, :sender, ConfigHelper.sender()))
|
||||
|> subject(Map.get(opts, :subject, "Welcome to #{ConfigHelper.instance_name()}!"))
|
||||
|> html_body(Map.get(opts, :html, "Welcome to #{ConfigHelper.instance_name()}!"))
|
||||
|> text_body(Map.get(opts, :text, "Welcome to #{ConfigHelper.instance_name()}!"))
|
||||
end
|
||||
|
||||
def password_reset_email(user, token) when is_binary(token) do
|
||||
password_reset_url = Router.Helpers.reset_password_url(Endpoint, :reset, token)
|
||||
|
||||
html_body = """
|
||||
<h3>Reset your password at #{instance_name()}</h3>
|
||||
<p>Someone has requested password change for your account at #{instance_name()}.</p>
|
||||
<h3>Reset your password at #{ConfigHelper.instance_name()}</h3>
|
||||
<p>Someone has requested password change for your account at #{ConfigHelper.instance_name()}.</p>
|
||||
<p>If it was you, visit the following link to proceed: <a href="#{password_reset_url}">reset password</a>.</p>
|
||||
<p>If it was someone else, nothing to worry about: your data is secure and your password has not been changed.</p>
|
||||
"""
|
||||
|
||||
new()
|
||||
|> to(recipient(user))
|
||||
|> from(sender())
|
||||
|> from(ConfigHelper.sender())
|
||||
|> subject("Password reset")
|
||||
|> html_body(html_body)
|
||||
end
|
||||
@ -59,15 +58,15 @@ defmodule Pleroma.Emails.UserEmail do
|
||||
)
|
||||
|
||||
html_body = """
|
||||
<h3>You are invited to #{instance_name()}</h3>
|
||||
<p>#{user.name} invites you to join #{instance_name()}, an instance of Pleroma federated social networking platform.</p>
|
||||
<h3>You are invited to #{ConfigHelper.instance_name()}</h3>
|
||||
<p>#{user.name} invites you to join #{ConfigHelper.instance_name()}, an instance of Pleroma federated social networking platform.</p>
|
||||
<p>Click the following link to register: <a href="#{registration_url}">accept invitation</a>.</p>
|
||||
"""
|
||||
|
||||
new()
|
||||
|> to(recipient(to_email, to_name))
|
||||
|> from(sender())
|
||||
|> subject("Invitation to #{instance_name()}")
|
||||
|> from(ConfigHelper.sender())
|
||||
|> subject("Invitation to #{ConfigHelper.instance_name()}")
|
||||
|> html_body(html_body)
|
||||
end
|
||||
|
||||
@ -81,27 +80,27 @@ defmodule Pleroma.Emails.UserEmail do
|
||||
)
|
||||
|
||||
html_body = """
|
||||
<h3>Thank you for registering on #{instance_name()}</h3>
|
||||
<h3>Thank you for registering on #{ConfigHelper.instance_name()}!</h3>
|
||||
<p>Email confirmation is required to activate the account.</p>
|
||||
<p>Please click the following link to <a href="#{confirmation_url}">activate your account</a>.</p>
|
||||
"""
|
||||
|
||||
new()
|
||||
|> to(recipient(user))
|
||||
|> from(sender())
|
||||
|> subject("#{instance_name()} account confirmation")
|
||||
|> from(ConfigHelper.sender())
|
||||
|> subject("#{ConfigHelper.instance_name()} account confirmation")
|
||||
|> html_body(html_body)
|
||||
end
|
||||
|
||||
def approval_pending_email(user) do
|
||||
html_body = """
|
||||
<h3>Awaiting Approval</h3>
|
||||
<p>Your account at #{instance_name()} is being reviewed by staff. You will receive another email once your account is approved.</p>
|
||||
<p>Your account at #{ConfigHelper.instance_name()} is being reviewed by staff. You will receive another email once your account is approved.</p>
|
||||
"""
|
||||
|
||||
new()
|
||||
|> to(recipient(user))
|
||||
|> from(sender())
|
||||
|> from(ConfigHelper.sender())
|
||||
|> subject("Your account is awaiting approval")
|
||||
|> html_body(html_body)
|
||||
end
|
||||
@ -109,14 +108,14 @@ defmodule Pleroma.Emails.UserEmail do
|
||||
def successful_registration_email(user) do
|
||||
html_body = """
|
||||
<h3>Hello @#{user.nickname},</h3>
|
||||
<p>Your account at #{instance_name()} has been registered successfully.</p>
|
||||
<p>Your account at #{ConfigHelper.instance_name()} has been registered successfully.</p>
|
||||
<p>No further action is required to activate your account.</p>
|
||||
"""
|
||||
|
||||
new()
|
||||
|> to(recipient(user))
|
||||
|> from(sender())
|
||||
|> subject("Account registered on #{instance_name()}")
|
||||
|> from(ConfigHelper.sender())
|
||||
|> subject("Account registered on #{ConfigHelper.instance_name()}")
|
||||
|> html_body(html_body)
|
||||
end
|
||||
|
||||
@ -168,7 +167,7 @@ defmodule Pleroma.Emails.UserEmail do
|
||||
logo = Config.get([__MODULE__, :logo])
|
||||
|
||||
html_data = %{
|
||||
instance: instance_name(),
|
||||
instance: ConfigHelper.instance_name(),
|
||||
user: user,
|
||||
mentions: mentions,
|
||||
followers: followers,
|
||||
@ -185,8 +184,8 @@ defmodule Pleroma.Emails.UserEmail do
|
||||
|
||||
new()
|
||||
|> to(recipient(user))
|
||||
|> from(sender())
|
||||
|> subject("Your digest from #{instance_name()}")
|
||||
|> from(ConfigHelper.sender())
|
||||
|> subject("Your digest from #{ConfigHelper.instance_name()}")
|
||||
|> put_layout(false)
|
||||
|> render_body("digest.html", html_data)
|
||||
|> attachment(Swoosh.Attachment.new(logo_path, filename: "logo.svg", type: :inline))
|
||||
@ -238,7 +237,7 @@ defmodule Pleroma.Emails.UserEmail do
|
||||
|
||||
new()
|
||||
|> to(recipient(user))
|
||||
|> from(sender())
|
||||
|> from(ConfigHelper.sender())
|
||||
|> subject("Your account archive is ready")
|
||||
|> html_body(html_body)
|
||||
end
|
||||
|
@ -12,13 +12,14 @@ defmodule Pleroma.Gopher.Server do
|
||||
port = Keyword.get(config, :port, 1234)
|
||||
|
||||
if Keyword.get(config, :enabled, false) do
|
||||
GenServer.start_link(__MODULE__, [ip, port], [])
|
||||
GenServer.start_link(__MODULE__, [ip, port])
|
||||
else
|
||||
Logger.info("Gopher server disabled")
|
||||
:ignore
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init([ip, port]) do
|
||||
Logger.info("Starting gopher server on #{port}")
|
||||
|
||||
@ -31,8 +32,14 @@ defmodule Pleroma.Gopher.Server do
|
||||
[]
|
||||
)
|
||||
|
||||
Process.flag(:trap_exit, true)
|
||||
{:ok, %{ip: ip, port: port}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def terminate(_reason, _state) do
|
||||
:ranch.stop_listener(:gopher)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Pleroma.Gopher.Server.ProtocolHandler do
|
||||
|
19
lib/pleroma/gun/gun_supervisor.ex
Normal file
19
lib/pleroma/gun/gun_supervisor.ex
Normal file
@ -0,0 +1,19 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Gun.GunSupervisor do
|
||||
use Supervisor
|
||||
|
||||
def start_link(_) do
|
||||
Supervisor.start_link(__MODULE__, :no_args)
|
||||
end
|
||||
|
||||
def init(_) do
|
||||
children =
|
||||
Pleroma.Gun.ConnectionPool.children() ++
|
||||
[{Task, &Pleroma.HTTP.AdapterHelper.Gun.limiter_setup/0}]
|
||||
|
||||
Supervisor.init(children, strategy: :one_for_one)
|
||||
end
|
||||
end
|
@ -2,16 +2,20 @@
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Config.Helpers do
|
||||
defmodule Pleroma.Helpers.ConfigHelper do
|
||||
alias Pleroma.Config
|
||||
|
||||
require Logger
|
||||
|
||||
@spec instance_name() :: String.t() | nil
|
||||
def instance_name, do: Config.get([:instance, :name])
|
||||
|
||||
@spec sender() :: {String.t() | nil, String.t() | nil}
|
||||
def sender do
|
||||
{instance_name(), instance_notify_email()}
|
||||
end
|
||||
|
||||
defp instance_notify_email do
|
||||
Config.get([:instance, :notify_email]) || Config.get([:instance, :email])
|
||||
end
|
||||
|
||||
def sender do
|
||||
{instance_name(), instance_notify_email()}
|
||||
end
|
||||
end
|
63
lib/pleroma/helpers/server_ip_helper.ex
Normal file
63
lib/pleroma/helpers/server_ip_helper.ex
Normal file
@ -0,0 +1,63 @@
|
||||
defmodule Pleroma.Helpers.ServerIPHelper do
|
||||
@moduledoc """
|
||||
Module tries to get server real ip address from system or makes request to the remote server.
|
||||
"""
|
||||
|
||||
# Taken from https://ipinfo.io/bogon
|
||||
@bogon_ranges [
|
||||
"0.0.0.0/8",
|
||||
"10.0.0.0/8",
|
||||
"100.64.0.0/10",
|
||||
"127.0.0.0/8",
|
||||
"127.0.53.53/32",
|
||||
"169.254.0.0/16",
|
||||
"172.16.0.0/12",
|
||||
"192.0.0.0/24",
|
||||
"192.0.2.0/24",
|
||||
"192.168.0.0/16",
|
||||
"198.18.0.0/15",
|
||||
"198.51.100.0/24",
|
||||
"203.0.113.0/24",
|
||||
"224.0.0.0/4",
|
||||
"240.0.0.0/4",
|
||||
"255.255.255.255/32"
|
||||
]
|
||||
|> Enum.map(&InetCidr.parse/1)
|
||||
|
||||
@spec real_ip() :: {:ok, String.t()} | {:error, term()}
|
||||
def real_ip do
|
||||
if Pleroma.Config.get(:env) == :prod do
|
||||
from_system() || from_remote_server()
|
||||
else
|
||||
{:ok, "127.0.0.1"}
|
||||
end
|
||||
end
|
||||
|
||||
defp from_system do
|
||||
with {:ok, interfaces} <- :inet.getifaddrs(),
|
||||
{_name, addresses} <-
|
||||
Enum.find(interfaces, fn {_name, addresses} ->
|
||||
addr = Keyword.get(addresses, :addr)
|
||||
|
||||
Enum.all?([:up, :broadcast, :running], &(&1 in addresses[:flags])) and
|
||||
not Enum.any?(@bogon_ranges, &InetCidr.contains?(&1, addr))
|
||||
end) do
|
||||
ip =
|
||||
addresses
|
||||
|> Keyword.get(:addr)
|
||||
|> :inet.ntoa()
|
||||
|> to_string()
|
||||
|
||||
{:ok, ip}
|
||||
else
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp from_remote_server do
|
||||
with {:ok, %{body: body}} <- Pleroma.HTTP.get("https://api.myip.com") do
|
||||
%{"ip" => ip} = Jason.decode!(body)
|
||||
{:ok, ip}
|
||||
end
|
||||
end
|
||||
end
|
30
lib/pleroma/http/hackney_supervisor.ex
Normal file
30
lib/pleroma/http/hackney_supervisor.ex
Normal file
@ -0,0 +1,30 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.HTTP.HackneySupervisor do
|
||||
use Supervisor
|
||||
|
||||
def start_link(_) do
|
||||
Supervisor.start_link(__MODULE__, :no_arg)
|
||||
end
|
||||
|
||||
def init(_) do
|
||||
pools = [:federation, :media]
|
||||
|
||||
pools =
|
||||
if Pleroma.Config.get([Pleroma.Upload, :proxy_remote]) do
|
||||
[:upload | pools]
|
||||
else
|
||||
pools
|
||||
end
|
||||
|
||||
children =
|
||||
for pool <- pools do
|
||||
options = Pleroma.Config.get([:hackney_pools, pool])
|
||||
:hackney_pool.child_spec(pool, options)
|
||||
end
|
||||
|
||||
Supervisor.init(children, strategy: :one_for_one)
|
||||
end
|
||||
end
|
@ -8,8 +8,6 @@ defmodule Pleroma.Repo do
|
||||
adapter: Ecto.Adapters.Postgres,
|
||||
migration_timestamps: [type: :naive_datetime_usec]
|
||||
|
||||
use Ecto.Explain
|
||||
|
||||
import Ecto.Query
|
||||
require Logger
|
||||
|
||||
|
@ -2483,4 +2483,7 @@ defmodule Pleroma.User do
|
||||
|> where([u], u.local == true)
|
||||
|> Repo.aggregate(:count)
|
||||
end
|
||||
|
||||
@spec email_regex() :: Regex.t()
|
||||
def email_regex, do: @email_regex
|
||||
end
|
||||
|
@ -9,10 +9,9 @@ defmodule Pleroma.User.WelcomeEmail do
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Emails
|
||||
alias Pleroma.Helpers.ConfigHelper
|
||||
alias Pleroma.User
|
||||
|
||||
import Pleroma.Config.Helpers, only: [instance_name: 0]
|
||||
|
||||
@spec enabled?() :: boolean()
|
||||
def enabled?, do: Config.get([:welcome, :email, :enabled], false)
|
||||
|
||||
@ -24,7 +23,7 @@ defmodule Pleroma.User.WelcomeEmail do
|
||||
end
|
||||
|
||||
defp email_options(user) do
|
||||
bindings = [user: user, instance_name: instance_name()]
|
||||
bindings = [user: user, instance_name: ConfigHelper.instance_name()]
|
||||
|
||||
%{}
|
||||
|> add_sender(Config.get([:welcome, :email, :sender], nil))
|
||||
@ -45,7 +44,7 @@ defmodule Pleroma.User.WelcomeEmail do
|
||||
end
|
||||
|
||||
defp add_sender(opts, sender) when is_binary(sender) do
|
||||
add_sender(opts, {instance_name(), sender})
|
||||
add_sender(opts, {ConfigHelper.instance_name(), sender})
|
||||
end
|
||||
|
||||
defp add_sender(opts, _), do: opts
|
||||
|
@ -392,14 +392,14 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||
|
||||
def restart(conn, _params) do
|
||||
with :ok <- configurable_from_database() do
|
||||
Restarter.Pleroma.restart(Config.get(:env), 50)
|
||||
Task.start(Pleroma.Application.ConfigDependentDeps, :restart_dependencies, [])
|
||||
|
||||
json(conn, %{})
|
||||
end
|
||||
end
|
||||
|
||||
def need_reboot(conn, _params) do
|
||||
json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()})
|
||||
json(conn, %{need_reboot: Pleroma.Application.ConfigDependentDeps.need_reboot?()})
|
||||
end
|
||||
|
||||
defp configurable_from_database do
|
||||
|
@ -5,19 +5,24 @@
|
||||
defmodule Pleroma.Web.AdminAPI.ConfigController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
|
||||
|
||||
alias Pleroma.Application
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.ConfigDB
|
||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
plug(OAuthScopesPlug, %{scopes: ["admin:write"]} when action == :update)
|
||||
plug(OAuthScopesPlug, %{scopes: ["admin:write"]} when action in [:update, :rollback])
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["admin:read"]}
|
||||
when action in [:show, :descriptions]
|
||||
when action in [:show, :descriptions, :versions]
|
||||
)
|
||||
|
||||
plug(:check_possibility_configuration_from_database when action != :descriptions)
|
||||
|
||||
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ConfigOperation
|
||||
@ -29,100 +34,111 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
|
||||
end
|
||||
|
||||
def show(conn, %{only_db: true}) do
|
||||
with :ok <- configurable_from_database() do
|
||||
configs = Pleroma.Repo.all(ConfigDB)
|
||||
configs = ConfigDB.all_with_db()
|
||||
|
||||
render(conn, "index.json", %{
|
||||
configs: configs,
|
||||
need_reboot: Restarter.Pleroma.need_reboot?()
|
||||
})
|
||||
end
|
||||
render(conn, "index.json", %{
|
||||
configs: configs,
|
||||
need_reboot: Application.ConfigDependentDeps.need_reboot?()
|
||||
})
|
||||
end
|
||||
|
||||
def show(conn, _params) do
|
||||
with :ok <- configurable_from_database() do
|
||||
configs = ConfigDB.get_all_as_keyword()
|
||||
defaults = Config.Holder.default_config()
|
||||
changes = ConfigDB.all_with_db()
|
||||
|
||||
merged =
|
||||
Config.Holder.default_config()
|
||||
|> ConfigDB.merge(configs)
|
||||
|> Enum.map(fn {group, values} ->
|
||||
Enum.map(values, fn {key, value} ->
|
||||
db =
|
||||
if configs[group][key] do
|
||||
ConfigDB.get_db_keys(configs[group][key], key)
|
||||
end
|
||||
{changes_values_merged_with_defaults, remaining_defaults} =
|
||||
ConfigDB.reduce_defaults_and_merge_with_changes(changes, defaults)
|
||||
|
||||
db_value = configs[group][key]
|
||||
changes_merged_with_defaults =
|
||||
ConfigDB.from_keyword_to_structs(remaining_defaults, changes_values_merged_with_defaults)
|
||||
|
||||
merged_value =
|
||||
if not is_nil(db_value) and Keyword.keyword?(db_value) and
|
||||
ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do
|
||||
ConfigDB.merge_group(group, key, value, db_value)
|
||||
else
|
||||
value
|
||||
end
|
||||
|
||||
%ConfigDB{
|
||||
group: group,
|
||||
key: key,
|
||||
value: merged_value
|
||||
}
|
||||
|> Pleroma.Maps.put_if_present(:db, db)
|
||||
end)
|
||||
end)
|
||||
|> List.flatten()
|
||||
|
||||
render(conn, "index.json", %{
|
||||
configs: merged,
|
||||
need_reboot: Restarter.Pleroma.need_reboot?()
|
||||
})
|
||||
end
|
||||
render(conn, "index.json", %{
|
||||
configs: changes_merged_with_defaults,
|
||||
need_reboot: Application.ConfigDependentDeps.need_reboot?()
|
||||
})
|
||||
end
|
||||
|
||||
def update(%{body_params: %{configs: configs}} = conn, _) do
|
||||
with :ok <- configurable_from_database() do
|
||||
results =
|
||||
configs
|
||||
|> Enum.filter(&whitelisted_config?/1)
|
||||
|> Enum.map(fn
|
||||
%{group: group, key: key, delete: true} = params ->
|
||||
ConfigDB.delete(%{group: group, key: key, subkeys: params[:subkeys]})
|
||||
result =
|
||||
configs
|
||||
|> Enum.filter(&whitelisted_config?/1)
|
||||
|> Enum.map(&Config.Converter.to_elixir_types/1)
|
||||
|> Config.Versioning.new_version()
|
||||
|
||||
%{group: group, key: key, value: value} ->
|
||||
ConfigDB.update_or_create(%{group: group, key: key, value: value})
|
||||
end)
|
||||
|> Enum.reject(fn {result, _} -> result == :error end)
|
||||
case result do
|
||||
{:ok, changes} ->
|
||||
inserts_and_deletions =
|
||||
changes
|
||||
|> Enum.reduce([], fn
|
||||
{{operation, _, _}, %ConfigDB{} = change}, acc
|
||||
when operation in [:insert_or_update, :delete_or_update] ->
|
||||
if Ecto.get_meta(change, :state) == :deleted do
|
||||
[change | acc]
|
||||
else
|
||||
if change.group == :pleroma and
|
||||
change.key in ConfigDB.pleroma_not_keyword_values() do
|
||||
[%{change | db: [change.key]} | acc]
|
||||
else
|
||||
[%{change | db: Keyword.keys(change.value)} | acc]
|
||||
end
|
||||
end
|
||||
|
||||
{deleted, updated} =
|
||||
results
|
||||
|> Enum.map(fn {:ok, %{key: key, value: value} = config} ->
|
||||
Map.put(config, :db, ConfigDB.get_db_keys(value, key))
|
||||
end)
|
||||
|> Enum.split_with(&(Ecto.get_meta(&1, :state) == :deleted))
|
||||
_, acc ->
|
||||
acc
|
||||
end)
|
||||
|
||||
Config.TransferTask.load_and_update_env(deleted, false)
|
||||
Application.Environment.update(inserts_and_deletions)
|
||||
|
||||
if not Restarter.Pleroma.need_reboot?() do
|
||||
changed_reboot_settings? =
|
||||
(updated ++ deleted)
|
||||
|> Enum.any?(&Config.TransferTask.pleroma_need_restart?(&1.group, &1.key, &1.value))
|
||||
render(conn, "index.json", %{
|
||||
configs: Enum.reject(inserts_and_deletions, &(Ecto.get_meta(&1, :state) == :deleted)),
|
||||
need_reboot: Application.ConfigDependentDeps.need_reboot?()
|
||||
})
|
||||
|
||||
if changed_reboot_settings?, do: Restarter.Pleroma.need_reboot()
|
||||
end
|
||||
{:error, error} ->
|
||||
{:error, "Updating config failed: #{inspect(error)}"}
|
||||
|
||||
render(conn, "index.json", %{
|
||||
configs: updated,
|
||||
need_reboot: Restarter.Pleroma.need_reboot?()
|
||||
})
|
||||
{:error, _, {error, operation}, _} ->
|
||||
{:error,
|
||||
"Updating config failed: #{inspect(error)}, group: #{operation[:group]}, key: #{
|
||||
operation[:key]
|
||||
}, value: #{inspect(operation[:value])}"}
|
||||
end
|
||||
end
|
||||
|
||||
defp configurable_from_database do
|
||||
def rollback(conn, %{id: id}) do
|
||||
case Config.Versioning.rollback_by_id(id) do
|
||||
{:ok, _} ->
|
||||
json_response(conn, :no_content, "")
|
||||
|
||||
{:error, :not_found} ->
|
||||
{:error, :not_found}
|
||||
|
||||
{:error, error} ->
|
||||
{:error, "Rollback is not possible: #{inspect(error)}"}
|
||||
|
||||
{:error, _, {error, operation}, _} ->
|
||||
{:error,
|
||||
"Rollback is not possible, backup restore error: #{inspect(error)}, operation error: #{
|
||||
inspect(operation)
|
||||
}"}
|
||||
end
|
||||
end
|
||||
|
||||
def versions(conn, _) do
|
||||
versions = Pleroma.Config.Version.all()
|
||||
|
||||
render(conn, "index.json", %{versions: versions})
|
||||
end
|
||||
|
||||
defp check_possibility_configuration_from_database(conn, _) do
|
||||
if Config.get(:configurable_from_database) do
|
||||
:ok
|
||||
conn
|
||||
else
|
||||
{:error, "You must enable configurable_from_database in your config file."}
|
||||
Pleroma.Web.AdminAPI.FallbackController.call(
|
||||
conn,
|
||||
{:error, "You must enable configurable_from_database in your config file."}
|
||||
)
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -5,8 +5,6 @@
|
||||
defmodule Pleroma.Web.AdminAPI.ConfigView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
alias Pleroma.ConfigDB
|
||||
|
||||
def render("index.json", %{configs: configs} = params) do
|
||||
%{
|
||||
configs: render_many(configs, __MODULE__, "show.json", as: :config),
|
||||
@ -14,17 +12,23 @@ defmodule Pleroma.Web.AdminAPI.ConfigView do
|
||||
}
|
||||
end
|
||||
|
||||
def render("show.json", %{config: config}) do
|
||||
map = %{
|
||||
key: ConfigDB.to_json_types(config.key),
|
||||
group: ConfigDB.to_json_types(config.group),
|
||||
value: ConfigDB.to_json_types(config.value)
|
||||
def render("index.json", %{versions: versions}) do
|
||||
%{
|
||||
versions: render_many(versions, __MODULE__, "show.json", as: :version)
|
||||
}
|
||||
end
|
||||
|
||||
if config.db != [] do
|
||||
Map.put(map, :db, config.db)
|
||||
else
|
||||
map
|
||||
end
|
||||
def render("show.json", %{config: config}) do
|
||||
config
|
||||
|> Map.take([:group, :key, :value, :db])
|
||||
|> Map.new(fn
|
||||
{k, v} -> {k, Pleroma.Config.Converter.to_json_types(v)}
|
||||
end)
|
||||
end
|
||||
|
||||
def render("show.json", %{version: version}) do
|
||||
version
|
||||
|> Map.take([:id, :current])
|
||||
|> Map.put(:inserted_at, Pleroma.Web.CommonAPI.Utils.to_masto_date(version.inserted_at))
|
||||
end
|
||||
end
|
||||
|
@ -53,7 +53,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation do
|
||||
type: :object,
|
||||
properties: %{
|
||||
group: %Schema{type: :string},
|
||||
key: %Schema{type: :string},
|
||||
key: %Schema{type: :string, nullable: true},
|
||||
value: any(),
|
||||
delete: %Schema{type: :boolean},
|
||||
subkeys: %Schema{type: :array, items: %Schema{type: :string}}
|
||||
@ -107,6 +107,56 @@ defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation do
|
||||
}
|
||||
end
|
||||
|
||||
def rollback_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Config"],
|
||||
summary: "Rollback config changes.",
|
||||
operationId: "AdminAPI.ConfigController.rollback",
|
||||
security: [%{"oAuth" => ["write"]}],
|
||||
parameters: [
|
||||
Operation.parameter(:id, :path, %Schema{type: :integer}, "Version id to rollback",
|
||||
required: true
|
||||
)
|
||||
| admin_api_params()
|
||||
],
|
||||
responses: %{
|
||||
204 => no_content_response(),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def versions_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Config"],
|
||||
summary: "Get list with config versions.",
|
||||
operationId: "AdminAPI.ConfigController.versions",
|
||||
security: [%{"oAuth" => ["read"]}],
|
||||
parameters: admin_api_params(),
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Config Version", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
versions: %Schema{
|
||||
type: :array,
|
||||
items: %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{type: :integer},
|
||||
current: %Schema{type: :boolean},
|
||||
inserted_at: %Schema{type: :string, format: :"date-time"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp any do
|
||||
%Schema{
|
||||
oneOf: [
|
||||
@ -129,7 +179,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation do
|
||||
type: :object,
|
||||
properties: %{
|
||||
group: %Schema{type: :string},
|
||||
key: %Schema{type: :string},
|
||||
key: %Schema{type: :string, nullable: true},
|
||||
value: any()
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright _ 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.RichMedia.Helpers do
|
||||
|
@ -232,6 +232,8 @@ defmodule Pleroma.Web.Router do
|
||||
get("/config", ConfigController, :show)
|
||||
post("/config", ConfigController, :update)
|
||||
get("/config/descriptions", ConfigController, :descriptions)
|
||||
get("/config/versions", ConfigController, :versions)
|
||||
get("/config/versions/rollback/:id", ConfigController, :rollback)
|
||||
get("/need_reboot", AdminAPIController, :need_reboot)
|
||||
get("/restart", AdminAPIController, :restart)
|
||||
|
||||
|
13
mix.exs
13
mix.exs
@ -77,9 +77,8 @@ defmodule Pleroma.Mixfile do
|
||||
:logger,
|
||||
:runtime_tools,
|
||||
:comeonin,
|
||||
:quack,
|
||||
:fast_sanitize,
|
||||
:ssl
|
||||
:ssl,
|
||||
:fast_sanitize
|
||||
],
|
||||
included_applications: [:ex_syslogger]
|
||||
]
|
||||
@ -87,8 +86,8 @@ defmodule Pleroma.Mixfile do
|
||||
|
||||
# Specifies which paths to compile per environment.
|
||||
defp elixirc_paths(:benchmark), do: ["lib", "benchmarks"]
|
||||
defp elixirc_paths(:test), do: ["lib", "test/support"]
|
||||
defp elixirc_paths(_), do: ["lib"]
|
||||
defp elixirc_paths(:test), do: ["lib", "test/support", "installer/pleroma"]
|
||||
defp elixirc_paths(_), do: ["lib", "installer/pleroma"]
|
||||
|
||||
defp warnings_as_errors(:prod), do: false
|
||||
defp warnings_as_errors(_), do: true
|
||||
@ -121,8 +120,7 @@ defmodule Pleroma.Mixfile do
|
||||
{:phoenix_pubsub, "~> 2.0"},
|
||||
{:phoenix_ecto, "~> 4.0"},
|
||||
{:ecto_enum, "~> 1.4"},
|
||||
{:ecto_explain, "~> 0.1.2"},
|
||||
{:ecto_sql, "~> 3.4.4"},
|
||||
{:ecto_sql, "~> 3.5"},
|
||||
{:postgrex, ">= 0.15.5"},
|
||||
{:oban, "~> 2.3.4"},
|
||||
{:gettext, "~> 0.18"},
|
||||
@ -192,7 +190,6 @@ defmodule Pleroma.Mixfile do
|
||||
{:captcha,
|
||||
git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git",
|
||||
ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"},
|
||||
{:restarter, path: "./restarter"},
|
||||
{:majic,
|
||||
git: "https://git.pleroma.social/pleroma/elixir-libraries/majic.git",
|
||||
ref: "289cda1b6d0d70ccb2ba508a2b0bd24638db2880"},
|
||||
|
6
mix.lock
6
mix.lock
@ -29,10 +29,10 @@
|
||||
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
|
||||
"earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"},
|
||||
"ecto": {:hex, :ecto, "3.4.6", "08f7afad3257d6eb8613309af31037e16c36808dfda5a3cd0cb4e9738db030e4", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6f13a9e2a62e75c2dcfc7207bfc65645ab387af8360db4c89fee8b5a4bf3f70b"},
|
||||
"ecto": {:hex, :ecto, "3.5.8", "8ebf12be6016cb99313348ba7bb4612f4114b9a506d6da79a2adc7ef449340bc", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ea0be182ea8922eb7742e3ae8e71b67ee00ae177de1bf76210299a5f16ba4c77"},
|
||||
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
|
||||
"ecto_explain": {:hex, :ecto_explain, "0.1.2", "a9d504cbd4adc809911f796d5ef7ebb17a576a6d32286c3d464c015bd39d5541", [:mix], [], "hexpm", "1d0e7798ae30ecf4ce34e912e5354a0c1c832b7ebceba39298270b9a9f316330"},
|
||||
"ecto_sql": {:hex, :ecto_sql, "3.4.5", "30161f81b167d561a9a2df4329c10ae05ff36eca7ccc84628f2c8b9fa1e43323", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31990c6a3579b36a3c0841d34a94c275e727de8b84f58509da5f1b2032c98ac2"},
|
||||
"ecto_sql": {:hex, :ecto_sql, "3.5.4", "a9e292c40bd79fff88885f95f1ecd7b2516e09aa99c7dd0201aa84c54d2358e4", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.5.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1fff1a28a898d7bbef263f1f3ea425b04ba9f33816d843238c84eff883347343"},
|
||||
"eimp": {:hex, :eimp, "1.0.14", "fc297f0c7e2700457a95a60c7010a5f1dcb768a083b6d53f49cd94ab95a28f22", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "501133f3112079b92d9e22da8b88bf4f0e13d4d67ae9c15c42c30bd25ceb83b6"},
|
||||
"elixir_make": {:hex, :elixir_make, "0.6.2", "7dffacd77dec4c37b39af867cedaabb0b59f6a871f89722c25b28fcd4bd70530", [:mix], [], "hexpm", "03e49eadda22526a7e5279d53321d1cced6552f344ba4e03e619063de75348d9"},
|
||||
"esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"},
|
||||
@ -98,7 +98,7 @@
|
||||
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"},
|
||||
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"},
|
||||
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
|
||||
"postgrex": {:hex, :postgrex, "0.15.7", "724410acd48abac529d0faa6c2a379fb8ae2088e31247687b16cacc0e0883372", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "88310c010ff047cecd73d5ceca1d99205e4b1ab1b9abfdab7e00f5c9d20ef8f9"},
|
||||
"postgrex": {:hex, :postgrex, "0.15.8", "f5e782bbe5e8fa178d5e3cd1999c857dc48eda95f0a4d7f7bd92a50e84a0d491", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "698fbfacea34c4cf22c8281abeb5cf68d99628d541874f085520ab3b53d356fe"},
|
||||
"pot": {:hex, :pot, "0.11.0", "61bad869a94534739dd4614a25a619bc5c47b9970e9a0ea5bef4628036fc7a16", [:rebar3], [], "hexpm", "57ee6ee6bdeb639661ffafb9acefe3c8f966e45394de6a766813bb9e1be4e54b"},
|
||||
"prometheus": {:hex, :prometheus, "4.6.0", "20510f381db1ccab818b4cf2fac5fa6ab5cc91bc364a154399901c001465f46f", [:mix, :rebar3], [], "hexpm", "4905fd2992f8038eccd7aa0cd22f40637ed618c0bed1f75c05aacec15b7545de"},
|
||||
"prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8d66289f77f913b37eda81fd287340c17e61a447549deb28efc254532b2bed82"},
|
||||
|
@ -1,6 +1,7 @@
|
||||
defmodule Pleroma.Repo.Migrations.AddContextIndex do
|
||||
use Ecto.Migration
|
||||
@disable_ddl_transaction true
|
||||
@disable_migration_lock true
|
||||
|
||||
def change do
|
||||
create(
|
||||
|
@ -1,6 +1,7 @@
|
||||
defmodule Pleroma.Repo.Migrations.AddFTSIndexToActivities do
|
||||
use Ecto.Migration
|
||||
@disable_ddl_transaction true
|
||||
@disable_migration_lock true
|
||||
|
||||
def change do
|
||||
create(
|
||||
|
@ -2,6 +2,7 @@ defmodule Pleroma.Repo.Migrations.AddTagIndex do
|
||||
use Ecto.Migration
|
||||
|
||||
@disable_ddl_transaction true
|
||||
@disable_migration_lock true
|
||||
|
||||
def change do
|
||||
create(
|
||||
|
@ -2,6 +2,7 @@ defmodule Pleroma.Repo.Migrations.AddSecondObjectIndexToActivty do
|
||||
use Ecto.Migration
|
||||
|
||||
@disable_ddl_transaction true
|
||||
@disable_migration_lock true
|
||||
|
||||
def change do
|
||||
drop_if_exists(
|
||||
|
@ -2,6 +2,7 @@ defmodule Pleroma.Repo.Migrations.AddObjectActorIndex do
|
||||
use Ecto.Migration
|
||||
|
||||
@disable_ddl_transaction true
|
||||
@disable_migration_lock true
|
||||
|
||||
def change do
|
||||
create(
|
||||
|
@ -2,6 +2,7 @@ defmodule Pleroma.Repo.Migrations.AddActorToActivity do
|
||||
use Ecto.Migration
|
||||
|
||||
@disable_ddl_transaction true
|
||||
@disable_migration_lock true
|
||||
|
||||
def up do
|
||||
alter table(:activities) do
|
||||
|
@ -1,6 +1,7 @@
|
||||
defmodule Pleroma.Repo.Migrations.AddSortIndexToActivities do
|
||||
use Ecto.Migration
|
||||
@disable_ddl_transaction true
|
||||
@disable_migration_lock true
|
||||
|
||||
def change do
|
||||
create(index(:activities, ["id desc nulls last"], concurrently: true))
|
||||
|
@ -2,6 +2,8 @@ defmodule Pleroma.Repo.Migrations.AddFollowerAddressIndexToUsers do
|
||||
use Ecto.Migration
|
||||
|
||||
@disable_ddl_transaction true
|
||||
@disable_migration_lock true
|
||||
|
||||
def change do
|
||||
create(index(:users, [:follower_address], concurrently: true))
|
||||
create(index(:users, [:following], concurrently: true, using: :gin))
|
||||
|
@ -1,6 +1,7 @@
|
||||
defmodule Pleroma.Repo.Migrations.ModifyActivityIndex do
|
||||
use Ecto.Migration
|
||||
@disable_ddl_transaction true
|
||||
@disable_migration_lock true
|
||||
|
||||
def change do
|
||||
create(index(:activities, ["id desc nulls last", "local"], concurrently: true))
|
||||
|
@ -1,6 +1,7 @@
|
||||
defmodule Pleroma.Repo.Migrations.CreateApidHostExtractionIndex do
|
||||
use Ecto.Migration
|
||||
@disable_ddl_transaction true
|
||||
@disable_migration_lock true
|
||||
|
||||
def change do
|
||||
create(
|
||||
|
@ -1,6 +1,7 @@
|
||||
defmodule Pleroma.Repo.Migrations.CreateActivitiesInReplyToIndex do
|
||||
use Ecto.Migration
|
||||
@disable_ddl_transaction true
|
||||
@disable_migration_lock true
|
||||
|
||||
def change do
|
||||
create(
|
||||
|
@ -1,6 +1,7 @@
|
||||
defmodule Pleroma.Repo.Migrations.AddVisibilityFunction do
|
||||
use Ecto.Migration
|
||||
@disable_ddl_transaction true
|
||||
@disable_migration_lock true
|
||||
|
||||
def up do
|
||||
definition = """
|
||||
|
@ -1,6 +1,7 @@
|
||||
defmodule Pleroma.Repo.Migrations.AddActivitiesLikesIndex do
|
||||
use Ecto.Migration
|
||||
@disable_ddl_transaction true
|
||||
@disable_migration_lock true
|
||||
|
||||
def change do
|
||||
create(
|
||||
|
@ -1,6 +1,7 @@
|
||||
defmodule Pleroma.Repo.Migrations.AddCorrectDMIndex do
|
||||
use Ecto.Migration
|
||||
@disable_ddl_transaction true
|
||||
@disable_migration_lock true
|
||||
|
||||
def up do
|
||||
drop_if_exists(
|
||||
|
@ -2,6 +2,8 @@ defmodule Pleroma.Repo.Migrations.AddIndexOnSubscribers do
|
||||
use Ecto.Migration
|
||||
|
||||
@disable_ddl_transaction true
|
||||
@disable_migration_lock true
|
||||
|
||||
def change do
|
||||
create(
|
||||
index(:users, ["(info->'subscribers')"],
|
||||
|
@ -2,6 +2,8 @@ defmodule Pleroma.Repo.Migrations.AddFollowingAddressIndexToUser do
|
||||
use Ecto.Migration
|
||||
|
||||
@disable_ddl_transaction true
|
||||
@disable_migration_lock true
|
||||
|
||||
def change do
|
||||
create(index(:users, [:following_address], concurrently: true))
|
||||
end
|
||||
|
@ -1,7 +1,6 @@
|
||||
defmodule Pleroma.Repo.Migrations.DataMigrationPopulateUserRelationships do
|
||||
use Ecto.Migration
|
||||
|
||||
alias Ecto.Adapters.SQL
|
||||
alias Pleroma.Repo
|
||||
|
||||
require Logger
|
||||
@ -25,7 +24,7 @@ defmodule Pleroma.Repo.Migrations.DataMigrationPopulateUserRelationships do
|
||||
Logger.info("Processing users.#{field}...")
|
||||
|
||||
{:ok, %{rows: field_rows}} =
|
||||
SQL.query(Repo, "SELECT id, #{field} FROM users WHERE #{field} != '{}'")
|
||||
Repo.query("SELECT id, #{field} FROM users WHERE #{field} != '{}'")
|
||||
|
||||
target_ap_ids =
|
||||
Enum.flat_map(
|
||||
@ -36,7 +35,7 @@ defmodule Pleroma.Repo.Migrations.DataMigrationPopulateUserRelationships do
|
||||
|
||||
# Selecting ids of all targets at once in order to reduce the number of SELECT queries
|
||||
{:ok, %{rows: target_ap_id_id}} =
|
||||
SQL.query(Repo, "SELECT ap_id, id FROM users WHERE ap_id = ANY($1)", [target_ap_ids])
|
||||
Repo.query("SELECT ap_id, id FROM users WHERE ap_id = ANY($1)", [target_ap_ids])
|
||||
|
||||
target_id_by_ap_id = Enum.into(target_ap_id_id, %{}, fn [k, v] -> {k, v} end)
|
||||
|
||||
|
@ -15,7 +15,7 @@ defmodule Pleroma.Repo.Migrations.AutolinkerToLinkify do
|
||||
|
||||
defp move_config(%{} = old, %{} = new) do
|
||||
{:ok, _} = ConfigDB.update_or_create(new)
|
||||
{:ok, _} = ConfigDB.delete(old)
|
||||
{:ok, _} = ConfigDB.delete_or_update(old)
|
||||
:ok
|
||||
end
|
||||
|
||||
|
@ -4,7 +4,7 @@ defmodule Pleroma.Repo.Migrations.MoveActivityExpirationsToOban do
|
||||
import Ecto.Query, only: [from: 2]
|
||||
|
||||
def change do
|
||||
Pleroma.Config.Oban.warn()
|
||||
Pleroma.Config.DeprecationWarnings.check_oban_config()
|
||||
|
||||
Application.ensure_all_started(:oban)
|
||||
|
||||
|
@ -4,7 +4,7 @@ defmodule Pleroma.Repo.Migrations.MoveTokensExpirationIntoOban do
|
||||
import Ecto.Query, only: [from: 2]
|
||||
|
||||
def change do
|
||||
Pleroma.Config.Oban.warn()
|
||||
Pleroma.Config.DeprecationWarnings.check_oban_config()
|
||||
|
||||
Application.ensure_all_started(:oban)
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user