akkoma/lib/mix/tasks/pleroma/config.ex

470 lines
12 KiB
Elixir
Raw Normal View History

# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Config do
use Mix.Task
import Ecto.Query
import Mix.Pleroma
alias Pleroma.ConfigDB
alias Pleroma.Repo
alias Pleroma.Config.ConfigurableFromDatabase
@shortdoc "Manages the location of the config"
@moduledoc File.read!("docs/docs/administration/CLI_tasks/config.md")
2019-09-29 08:17:38 +00:00
def run(["migrate_to_db"]) do
check_configdb(fn ->
start_pleroma()
migrate_to_db()
end)
end
2019-09-29 08:17:38 +00:00
def run(["migrate_from_db" | options]) do
check_configdb(fn ->
start_pleroma()
{opts, _} =
OptionParser.parse!(options,
2021-03-23 11:23:37 +00:00
strict: [env: :string, delete: :boolean, path: :string],
aliases: [d: :delete]
)
migrate_from_db(opts)
end)
2020-01-18 13:55:33 +00:00
end
def run(["dump"]) do
check_configdb(fn ->
start_pleroma()
header = config_header()
settings =
ConfigDB
|> Repo.all()
|> Enum.sort()
unless settings == [] do
shell_info("#{header}")
Enum.each(settings, &dump(&1))
else
shell_error("No settings in ConfigDB.")
end
end)
end
def run(["dump", group, key]) do
check_configdb(fn ->
start_pleroma()
group = maybe_atomize(group)
key = maybe_atomize(key)
2020-12-03 16:34:23 +00:00
group
|> ConfigDB.get_by_group_and_key(key)
|> dump()
end)
end
def run(["dump", group]) do
check_configdb(fn ->
start_pleroma()
group = maybe_atomize(group)
dump_group(group)
end)
end
def run(["dump_to_file", group, key, fname]) do
check_configdb(fn ->
start_pleroma()
group = maybe_atomize(group)
key = maybe_atomize(key)
config = ConfigDB.get_by_group_and_key(group, key)
json =
%{
group: ConfigDB.to_json_types(config.group),
key: ConfigDB.to_json_types(config.key),
value: ConfigDB.to_json_types(config.value)
}
|> Jason.encode!()
|> Jason.Formatter.pretty_print()
File.write(fname, json)
shell_info("Wrote #{group}_#{key}.json")
end)
end
def run(["load_from_file", fname]) do
check_configdb(fn ->
start_pleroma()
json = File.read!(fname)
config = Jason.decode!(json)
group = ConfigDB.to_elixir_types(config["group"])
key = ConfigDB.to_elixir_types(config["key"])
value = ConfigDB.to_elixir_types(config["value"])
params = %{group: group, key: key, value: value}
ConfigDB.update_or_create(params)
shell_info("Loaded #{config["group"]}, #{config["key"]}")
end)
end
2020-11-25 23:49:36 +00:00
def run(["groups"]) do
check_configdb(fn ->
start_pleroma()
groups =
ConfigDB
|> distinct([c], true)
|> select([c], c.group)
|> Repo.all()
if length(groups) > 0 do
shell_info("The following configuration groups are set in ConfigDB:\r\n")
groups |> Enum.each(fn x -> shell_info("- #{x}") end)
shell_info("\r\n")
end
end)
end
def run(["reset", "--force"]) do
check_configdb(fn ->
start_pleroma()
truncatedb()
shell_info("The ConfigDB settings have been removed from the database.")
end)
end
def run(["reset"]) do
check_configdb(fn ->
start_pleroma()
shell_info("The following settings will be permanently removed:")
ConfigDB
|> Repo.all()
|> Enum.sort()
|> Enum.each(&dump(&1))
shell_error("\nTHIS CANNOT BE UNDONE!")
if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
truncatedb()
shell_info("The ConfigDB settings have been removed from the database.")
else
shell_error("No changes made.")
end
end)
end
def run(["delete", "--force", group, key]) do
start_pleroma()
group = maybe_atomize(group)
key = maybe_atomize(key)
with true <- key_exists?(group, key) do
shell_info("The following settings will be removed from ConfigDB:\n")
group
|> ConfigDB.get_by_group_and_key(key)
|> dump()
delete_key(group, key)
else
_ ->
shell_error("No settings in ConfigDB for #{inspect(group)}, #{inspect(key)}. Aborting.")
end
end
def run(["delete", "--force", group]) do
start_pleroma()
group = maybe_atomize(group)
with true <- group_exists?(group) do
shell_info("The following settings will be removed from ConfigDB:\n")
dump_group(group)
delete_group(group)
else
_ -> shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.")
end
end
def run(["delete", group, key]) do
start_pleroma()
group = maybe_atomize(group)
key = maybe_atomize(key)
with true <- key_exists?(group, key) do
shell_info("The following settings will be removed from ConfigDB:\n")
group
|> ConfigDB.get_by_group_and_key(key)
|> dump()
if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
delete_key(group, key)
else
shell_error("No changes made.")
end
else
_ ->
shell_error("No settings in ConfigDB for #{inspect(group)}, #{inspect(key)}. Aborting.")
end
end
def run(["delete", group]) do
start_pleroma()
group = maybe_atomize(group)
with true <- group_exists?(group) do
shell_info("The following settings will be removed from ConfigDB:\n")
dump_group(group)
if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
delete_group(group)
else
shell_error("No changes made.")
end
else
_ -> shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.")
end
end
# Primarily a developer tool to check nothing was missed from
# db configwhitelist
def run(["check-allowed"]) do
start_pleroma()
Pleroma.Docs.JSON.compile()
raw = Pleroma.Docs.JSON.compiled_descriptions()
whitelisted = Enum.filter(raw, &ConfigurableFromDatabase.whitelisted_config?/1)
raw_map = MapSet.new(raw)
whitelisted_map = MapSet.new(whitelisted)
IO.puts("Config keys defined in description.exs but not listed as explicitly allowed in the db")
IO.puts(" Please check that standard admins should not need to touch the listed settings whilst the server is live.")
IO.puts(" !! Please remember that admins are not neccesarily sysadmins nor are they immune to oauth/password leakage.")
IO.puts("-------------")
MapSet.difference(raw_map, whitelisted_map)
|> Enum.each(fn map ->
IO.puts("#{map[:group]}, #{map[:key]} (#{map[:label]})")
IO.puts(map[:db_exclusion_reason] || "No exclusion reason set")
IO.puts("++")
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
2020-01-21 14:49:22 +00:00
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
2020-01-18 13:55:33 +00:00
do_migrate_to_db(config_file)
else
2020-12-02 19:00:07 +00:00
_ ->
shell_error("Migration is not allowed until all deprecation warnings have been resolved.")
end
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()
2020-01-21 14:49:22 +00:00
custom_config =
config_file
|> read_file()
|> elem(0)
custom_config
|> Keyword.keys()
2020-01-21 14:49:22 +00:00
|> Enum.each(&create(&1, custom_config))
else
2020-01-18 13:55:33 +00:00
shell_info("To migrate settings, you must define custom settings in #{config_file}.")
end
end
defp create(group, settings) do
2020-01-21 14:49:22 +00:00
group
|> Pleroma.Config.Loader.filter_group(settings)
2019-09-29 08:17:38 +00:00
|> Enum.each(fn {key, value} ->
2020-05-31 07:46:02 +00:00
{:ok, _} = ConfigDB.update_or_create(%{group: group, key: key, value: value})
2019-09-29 08:17:38 +00:00
2020-01-18 13:55:33 +00:00
shell_info("Settings for key #{key} migrated.")
2019-09-29 08:17:38 +00:00
end)
shell_info("Settings for group #{inspect(group)} migrated.")
2019-09-29 08:17:38 +00:00
end
2020-01-21 14:49:22 +00:00
defp migrate_from_db(opts) do
env = opts[:env] || Pleroma.Config.get(:env)
2020-01-21 14:49:22 +00:00
2021-03-23 11:23:37 +00:00
filename = "#{env}.exported_from_db.secret.exs"
config_path =
2021-03-23 11:23:37 +00:00
cond do
opts[:path] ->
opts[:path]
Pleroma.Config.get(:release) ->
:config_path
|> Pleroma.Config.get()
|> Path.dirname()
true ->
"config"
end
2021-03-23 11:23:37 +00:00
|> Path.join(filename)
2020-01-21 14:49:22 +00:00
2021-03-23 11:23:37 +00:00
with {:ok, file} <- File.open(config_path, [:write, :utf8]) do
write_config(file, config_path, opts)
shell_info("Database configuration settings have been exported to #{config_path}")
else
_ ->
shell_error("Impossible to save settings to this directory #{Path.dirname(config_path)}")
2021-03-23 14:27:02 +00:00
tmp_config_path = Path.join(System.tmp_dir!(), filename)
2021-03-23 11:23:37 +00:00
file = File.open!(tmp_config_path)
shell_info(
"Saving database configuration settings to #{tmp_config_path}. Copy it to the #{Path.dirname(config_path)} manually."
2021-03-23 11:23:37 +00:00
)
2020-01-21 14:49:22 +00:00
2021-03-23 11:23:37 +00:00
write_config(file, tmp_config_path, opts)
end
end
defp write_config(file, path, opts) do
IO.write(file, config_header())
2019-09-29 08:17:38 +00:00
ConfigDB
|> Repo.all()
|> Enum.each(&write_and_delete(&1, file, opts[:delete]))
2020-07-13 15:32:17 +00:00
:ok = File.close(file)
2021-03-23 11:23:37 +00:00
System.cmd("mix", ["format", path])
2020-01-21 14:49:22 +00:00
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
2020-05-31 07:46:02 +00:00
value = inspect(config.value, limit: :infinity)
2020-01-21 14:49:22 +00:00
2020-05-31 07:46:02 +00:00
IO.write(file, "config #{inspect(config.group)}, #{inspect(config.key)}, #{value}\r\n\r\n")
2020-01-21 14:49:22 +00:00
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."
)
2020-01-21 14:49:22 +00:00
end
defp delete(_config, _), do: :ok
defp dump(%ConfigDB{} = config) do
value = inspect(config.value, limit: :infinity)
shell_info("config #{inspect(config.group)}, #{inspect(config.key)}, #{value}\r\n\r\n")
end
defp dump(_), do: :noop
defp dump_group(group) when is_atom(group) do
2020-12-03 16:34:23 +00:00
group
|> ConfigDB.get_all_by_group()
2020-12-03 16:03:14 +00:00
|> Enum.each(&dump/1)
end
2020-12-03 16:03:14 +00:00
defp group_exists?(group) do
2020-12-03 16:34:23 +00:00
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
2020-11-29 18:59:03 +00:00
defp maybe_atomize(arg) when is_atom(arg), do: arg
2020-12-07 17:22:07 +00:00
defp maybe_atomize(":" <> arg), do: maybe_atomize(arg)
2020-11-29 18:59:03 +00:00
defp maybe_atomize(arg) when is_binary(arg) do
if ConfigDB.module_name?(arg) do
2020-12-03 16:03:14 +00:00
String.to_existing_atom("Elixir." <> arg)
else
String.to_atom(arg)
2020-12-03 16:34:23 +00:00
end
end
defp check_configdb(callback) do
with true <- Pleroma.Config.get([:configurable_from_database]) do
callback.()
else
2020-12-02 19:00:07 +00:00
_ ->
shell_error(
"ConfigDB not enabled. Please check the value of :configurable_from_database in your configuration."
)
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()
2020-12-04 17:37:49 +00:00
|> Enum.each(&ConfigDB.delete/1)
end)
end
2020-12-03 15:58:24 +00:00
defp truncatedb do
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;")
Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;")
end
end