# Pleroma: A lightweight social networking server # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Config.TransferTask do use Task 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}, {:tesla, :adapter} ] @reboot_time_subkeys [ {:pleroma, Pleroma.Captcha, [:seconds_valid]}, {:pleroma, Pleroma.Upload, [:proxy_remote]}, {:pleroma, :instance, [:upload_limit]}, {:pleroma, :email_notifications, [:digest]}, {:pleroma, :oauth2, [:clean_expired_tokens]}, {:pleroma, Pleroma.ActivityExpiration, [:enabled]}, {:pleroma, Pleroma.ScheduledActivity, [:enabled]}, {:pleroma, :gopher, [:enabled]} ] @reject [nil, :prometheus] def start_link(_) do load_and_update_env() if Pleroma.Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Repo) :ignore end @spec load_and_update_env([ConfigDB.t()]) :: :ok | false def load_and_update_env(deleted \\ [], restart_pleroma? \\ true) do with {:configurable, true} <- {:configurable, Pleroma.Config.get(:configurable_from_database)}, true <- Ecto.Adapters.SQL.table_exists?(Repo, "config"), started_applications <- Application.started_applications() do # We need to restart applications for loaded settings take effect in_db = Repo.all(ConfigDB) with_deleted = in_db ++ deleted reject_for_restart = if restart_pleroma?, do: @reject, else: [:pleroma | @reject] applications = with_deleted |> Enum.map(&merge_and_update(&1)) |> Enum.uniq() # TODO: some problem with prometheus after restart! |> Enum.reject(&(&1 in reject_for_restart)) # to be ensured that pleroma will be restarted last applications = if :pleroma in applications do List.delete(applications, :pleroma) ++ [:pleroma] else Restarter.Pleroma.rebooted() applications end Enum.each(applications, &restart(started_applications, &1, Pleroma.Config.get(:env))) :ok else {:configurable, false} -> Restarter.Pleroma.rebooted() end end defp group_for_restart(:logger, key, _, merged_value) do # change logger configuration in runtime, without restart if Keyword.keyword?(merged_value) and key not in [:compile_time_application, :backends, :compile_time_purge_matching] do Logger.configure_backend(key, merged_value) else Logger.configure([{key, merged_value}]) end nil end defp group_for_restart(:tesla, _, _, _), do: :pleroma defp group_for_restart(group, _, _, _) when group != :pleroma, do: group defp group_for_restart(group, key, value, _) do if pleroma_need_restart?(group, key, value) do group end end defp merge_and_update(setting) do try do key = ConfigDB.from_string(setting.key) group = ConfigDB.from_string(setting.group) default = Pleroma.Config.Holder.config(group, key) value = ConfigDB.from_binary(setting.value) merged_value = if Ecto.get_meta(setting, :state) == :deleted do default else if can_be_merged?(default, value) do ConfigDB.merge_group(group, key, default, value) else value end end :ok = update_env(group, key, merged_value) group_for_restart(group, key, value, merged_value) rescue error -> error_msg = "updating env causes error, group: " <> inspect(setting.group) <> " key: " <> inspect(setting.key) <> " value: " <> inspect(ConfigDB.from_binary(setting.value)) <> " error: " <> inspect(error) Logger.warn(error_msg) nil end end @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 update_env(group, key, nil), do: Application.delete_env(group, key) defp update_env(group, key, value), do: Application.put_env(group, key, value) 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