f657adf4600f7fae031d85f00d5985f4c38213f4
[akkoma] / lib / mix / tasks / pleroma / config.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Mix.Tasks.Pleroma.Config do
6 use Mix.Task
7
8 import Mix.Pleroma
9
10 alias Pleroma.ConfigDB
11 alias Pleroma.Repo
12
13 @shortdoc "Manages the location of the config"
14 @moduledoc File.read!("docs/administration/CLI_tasks/config.md")
15
16 def run(["migrate_to_db"]) do
17 start_pleroma()
18 migrate_to_db()
19 end
20
21 def run(["migrate_from_db" | options]) do
22 start_pleroma()
23
24 {opts, _} =
25 OptionParser.parse!(options,
26 strict: [env: :string, delete: :boolean],
27 aliases: [d: :delete]
28 )
29
30 migrate_from_db(opts)
31 end
32
33 def run(["dump"]) do
34 with true <- Pleroma.Config.get([:configurable_from_database]) do
35 start_pleroma()
36
37 header = config_header()
38
39 settings =
40 ConfigDB
41 |> Repo.all()
42 |> Enum.sort()
43
44 unless settings == [] do
45 shell_info("#{header}")
46
47 settings |> Enum.each(&dump(&1))
48 else
49 shell_error("No settings in ConfigDB.")
50 end
51 else
52 _ -> configdb_not_enabled()
53 end
54 end
55
56 def run(["dump", group, key]) do
57 with true <- Pleroma.Config.get([:configurable_from_database]) do
58 start_pleroma()
59
60 group = maybe_atomize(group)
61 key = maybe_atomize(key)
62
63 dump_key(group, key)
64 else
65 _ -> configdb_not_enabled()
66 end
67 end
68
69 def run(["dump", group]) do
70 with true <- Pleroma.Config.get([:configurable_from_database]) do
71 start_pleroma()
72
73 group = maybe_atomize(group)
74
75 dump_group(group)
76 else
77 _ -> configdb_not_enabled()
78 end
79 end
80
81 def run(["groups"]) do
82 with true <- Pleroma.Config.get([:configurable_from_database]) do
83 start_pleroma()
84
85 groups =
86 ConfigDB
87 |> Repo.all()
88 |> Enum.map(fn x -> x.group end)
89 |> Enum.sort()
90 |> Enum.uniq()
91
92 if length(groups) > 0 do
93 shell_info("The following configuration groups are set in ConfigDB:\r\n")
94 groups |> Enum.each(fn x -> shell_info("- #{x}") end)
95 shell_info("\r\n")
96 end
97 else
98 _ -> configdb_not_enabled()
99 end
100 end
101
102 def run(["reset"]) do
103 with true <- Pleroma.Config.get([:configurable_from_database]) do
104 start_pleroma()
105
106 shell_info("The following settings will be permanently removed:")
107
108 ConfigDB
109 |> Repo.all()
110 |> Enum.sort()
111 |> Enum.each(&dump(&1))
112
113 shell_error("\nTHIS CANNOT BE UNDONE!")
114
115 if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
116 Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;")
117 Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;")
118
119 shell_info("The ConfigDB settings have been removed from the database.")
120 else
121 shell_error("No changes made.")
122 end
123 else
124 _ -> configdb_not_enabled()
125 end
126 end
127
128 def run(["delete", group]) do
129 with true <- Pleroma.Config.get([:configurable_from_database]) do
130 start_pleroma()
131
132 group = maybe_atomize(group)
133
134 if group_exists?(group) do
135 shell_info("The following settings will be removed from ConfigDB:\n")
136
137 dump_group(group)
138
139 if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
140 ConfigDB
141 |> Repo.all()
142 |> Enum.filter(fn x ->
143 if x.group == group do
144 x |> delete(true)
145 end
146 end)
147 else
148 shell_error("No changes made.")
149 end
150 else
151 shell_error("No settings in ConfigDB for :#{group}. Aborting.")
152 end
153 else
154 _ -> configdb_not_enabled()
155 end
156 end
157
158 def run(["delete", group, key]) do
159 with true <- Pleroma.Config.get([:configurable_from_database]) do
160 start_pleroma()
161
162 group = maybe_atomize(group)
163 key = maybe_atomize(key)
164
165 if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
166 ConfigDB
167 |> Repo.all()
168 |> Enum.filter(fn x ->
169 if x.group == group and x.key == key do
170 x |> delete(true)
171 end
172 end)
173 else
174 shell_error("No changes made.")
175 end
176 else
177 _ -> configdb_not_enabled()
178 end
179 end
180
181 @spec migrate_to_db(Path.t() | nil) :: any()
182 def migrate_to_db(file_path \\ nil) do
183 with true <- Pleroma.Config.get([:configurable_from_database]),
184 :ok <- Pleroma.Config.DeprecationWarnings.warn() do
185 config_file =
186 if file_path do
187 file_path
188 else
189 if Pleroma.Config.get(:release) do
190 Pleroma.Config.get(:config_path)
191 else
192 "config/#{Pleroma.Config.get(:env)}.secret.exs"
193 end
194 end
195
196 do_migrate_to_db(config_file)
197 else
198 :error -> deprecation_error()
199 _ -> migration_error()
200 end
201 end
202
203 defp do_migrate_to_db(config_file) do
204 if File.exists?(config_file) do
205 shell_info("Migrating settings from file: #{Path.expand(config_file)}")
206 Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;")
207 Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;")
208
209 custom_config =
210 config_file
211 |> read_file()
212 |> elem(0)
213
214 custom_config
215 |> Keyword.keys()
216 |> Enum.each(&create(&1, custom_config))
217 else
218 shell_info("To migrate settings, you must define custom settings in #{config_file}.")
219 end
220 end
221
222 defp create(group, settings) do
223 group
224 |> Pleroma.Config.Loader.filter_group(settings)
225 |> Enum.each(fn {key, value} ->
226 {:ok, _} = ConfigDB.update_or_create(%{group: group, key: key, value: value})
227
228 shell_info("Settings for key #{key} migrated.")
229 end)
230
231 shell_info("Settings for group :#{group} migrated.")
232 end
233
234 defp migrate_from_db(opts) do
235 if Pleroma.Config.get([:configurable_from_database]) do
236 env = opts[:env] || Pleroma.Config.get(:env)
237
238 config_path =
239 if Pleroma.Config.get(:release) do
240 :config_path
241 |> Pleroma.Config.get()
242 |> Path.dirname()
243 else
244 "config"
245 end
246 |> Path.join("#{env}.exported_from_db.secret.exs")
247
248 file = File.open!(config_path, [:write, :utf8])
249
250 IO.write(file, config_header())
251
252 ConfigDB
253 |> Repo.all()
254 |> Enum.each(&write_and_delete(&1, file, opts[:delete]))
255
256 :ok = File.close(file)
257 System.cmd("mix", ["format", config_path])
258
259 shell_info(
260 "Database configuration settings have been exported to config/#{env}.exported_from_db.secret.exs"
261 )
262 else
263 migration_error()
264 end
265 end
266
267 defp migration_error do
268 shell_error(
269 "Migration is not allowed in config. You can change this behavior by setting `config :pleroma, configurable_from_database: true`"
270 )
271 end
272
273 defp deprecation_error do
274 shell_error("Migration is not allowed until all deprecation warnings have been resolved.")
275 end
276
277 if Code.ensure_loaded?(Config.Reader) do
278 defp config_header, do: "import Config\r\n\r\n"
279 defp read_file(config_file), do: Config.Reader.read_imports!(config_file)
280 else
281 defp config_header, do: "use Mix.Config\r\n\r\n"
282 defp read_file(config_file), do: Mix.Config.eval!(config_file)
283 end
284
285 defp write_and_delete(config, file, delete?) do
286 config
287 |> write(file)
288 |> delete(delete?)
289 end
290
291 defp write(config, file) do
292 value = inspect(config.value, limit: :infinity)
293
294 IO.write(file, "config #{inspect(config.group)}, #{inspect(config.key)}, #{value}\r\n\r\n")
295
296 config
297 end
298
299 defp delete(config, true) do
300 {:ok, _} = Repo.delete(config)
301
302 shell_info(
303 "config #{inspect(config.group)}, #{inspect(config.key)} deleted from the ConfigDB."
304 )
305 end
306
307 defp delete(_config, _), do: :ok
308
309 defp dump(%Pleroma.ConfigDB{} = config) do
310 value = inspect(config.value, limit: :infinity)
311
312 shell_info("config #{inspect(config.group)}, #{inspect(config.key)}, #{value}\r\n\r\n")
313 end
314
315 defp configdb_not_enabled do
316 shell_error(
317 "ConfigDB not enabled. Please check the value of :configurable_from_database in your configuration."
318 )
319 end
320
321 defp dump_key(group, key) when is_atom(group) and is_atom(key) do
322 ConfigDB
323 |> Repo.all()
324 |> Enum.filter(fn x ->
325 if x.group == group && x.key == key do
326 x |> dump
327 end
328 end)
329 end
330
331 defp dump_group(group) when is_atom(group) do
332 ConfigDB
333 |> Repo.all()
334 |> Enum.filter(fn x ->
335 if x.group == group do
336 x |> dump
337 end
338 end)
339 end
340
341 defp group_exists?(group) when is_atom(group) do
342 result =
343 ConfigDB
344 |> Repo.all()
345 |> Enum.filter(fn x ->
346 if x.group == group do
347 x
348 end
349 end)
350
351 unless result == [] do
352 true
353 else
354 false
355 end
356 end
357
358 def maybe_atomize(arg) when is_atom(arg), do: arg
359
360 def maybe_atomize(arg) when is_binary(arg) do
361 chars = String.codepoints(arg)
362
363 if "." in chars do
364 :"Elixir.#{arg}"
365 else
366 String.to_atom(arg)
367 end
368 end
369 end