Print whole config when resetting and include a scary looking message.
[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(["keys", group]) do
103 with true <- Pleroma.Config.get([:configurable_from_database]) do
104 start_pleroma()
105
106 group = maybe_atomize(group)
107
108 keys =
109 ConfigDB
110 |> Repo.all()
111 |> Enum.map(fn x ->
112 if x.group == group do
113 x.key
114 end
115 end)
116 |> Enum.sort()
117 |> Enum.uniq()
118 |> Enum.reject(fn x -> x == nil end)
119
120 if length(keys) > 0 do
121 shell_info("The following configuration keys under :#{group} are set in ConfigDB:\r\n")
122 keys |> Enum.each(fn x -> shell_info("- #{x}") end)
123 shell_info("\r\n")
124 end
125 else
126 _ -> configdb_not_enabled()
127 end
128 end
129
130 def run(["reset"]) do
131 with true <- Pleroma.Config.get([:configurable_from_database]) do
132 start_pleroma()
133
134 shell_info("The following settings will be permanently removed:")
135
136 ConfigDB
137 |> Repo.all()
138 |> Enum.sort()
139 |> Enum.each(&dump(&1))
140
141 shell_error("THIS CANNOT BE UNDONE!")
142
143 if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
144 Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;")
145 Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;")
146
147 shell_info("The ConfigDB settings have been removed from the database.")
148 else
149 shell_error("No changes made.")
150 end
151 else
152 _ -> configdb_not_enabled()
153 end
154 end
155
156 def run(["delete", group]) do
157 with true <- Pleroma.Config.get([:configurable_from_database]) do
158 start_pleroma()
159
160 group = maybe_atomize(group)
161
162 if group_exists?(group) do
163 shell_info("The following settings will be removed from ConfigDB:\n")
164
165 dump_group(group)
166
167 if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
168 ConfigDB
169 |> Repo.all()
170 |> Enum.filter(fn x ->
171 if x.group == group do
172 x |> delete(true)
173 end
174 end)
175 else
176 shell_error("No changes made.")
177 end
178 else
179 shell_error("No settings in ConfigDB for :#{group}. Aborting.")
180 end
181 else
182 _ -> configdb_not_enabled()
183 end
184 end
185
186 def run(["delete", group, key]) do
187 with true <- Pleroma.Config.get([:configurable_from_database]) do
188 start_pleroma()
189
190 group = maybe_atomize(group)
191 key = maybe_atomize(key)
192
193 if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
194 ConfigDB
195 |> Repo.all()
196 |> Enum.filter(fn x ->
197 if x.group == group and x.key == key do
198 x |> delete(true)
199 end
200 end)
201 else
202 shell_error("No changes made.")
203 end
204 else
205 _ -> configdb_not_enabled()
206 end
207 end
208
209 @spec migrate_to_db(Path.t() | nil) :: any()
210 def migrate_to_db(file_path \\ nil) do
211 with true <- Pleroma.Config.get([:configurable_from_database]),
212 :ok <- Pleroma.Config.DeprecationWarnings.warn() do
213 config_file =
214 if file_path do
215 file_path
216 else
217 if Pleroma.Config.get(:release) do
218 Pleroma.Config.get(:config_path)
219 else
220 "config/#{Pleroma.Config.get(:env)}.secret.exs"
221 end
222 end
223
224 do_migrate_to_db(config_file)
225 else
226 :error -> deprecation_error()
227 _ -> migration_error()
228 end
229 end
230
231 defp do_migrate_to_db(config_file) do
232 if File.exists?(config_file) do
233 shell_info("Migrating settings from file: #{Path.expand(config_file)}")
234 Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;")
235 Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;")
236
237 custom_config =
238 config_file
239 |> read_file()
240 |> elem(0)
241
242 custom_config
243 |> Keyword.keys()
244 |> Enum.each(&create(&1, custom_config))
245 else
246 shell_info("To migrate settings, you must define custom settings in #{config_file}.")
247 end
248 end
249
250 defp create(group, settings) do
251 group
252 |> Pleroma.Config.Loader.filter_group(settings)
253 |> Enum.each(fn {key, value} ->
254 {:ok, _} = ConfigDB.update_or_create(%{group: group, key: key, value: value})
255
256 shell_info("Settings for key #{key} migrated.")
257 end)
258
259 shell_info("Settings for group :#{group} migrated.")
260 end
261
262 defp migrate_from_db(opts) do
263 if Pleroma.Config.get([:configurable_from_database]) do
264 env = opts[:env] || Pleroma.Config.get(:env)
265
266 config_path =
267 if Pleroma.Config.get(:release) do
268 :config_path
269 |> Pleroma.Config.get()
270 |> Path.dirname()
271 else
272 "config"
273 end
274 |> Path.join("#{env}.exported_from_db.secret.exs")
275
276 file = File.open!(config_path, [:write, :utf8])
277
278 IO.write(file, config_header())
279
280 ConfigDB
281 |> Repo.all()
282 |> Enum.each(&write_and_delete(&1, file, opts[:delete]))
283
284 :ok = File.close(file)
285 System.cmd("mix", ["format", config_path])
286
287 shell_info(
288 "Database configuration settings have been exported to config/#{env}.exported_from_db.secret.exs"
289 )
290 else
291 migration_error()
292 end
293 end
294
295 defp migration_error do
296 shell_error(
297 "Migration is not allowed in config. You can change this behavior by setting `config :pleroma, configurable_from_database: true`"
298 )
299 end
300
301 defp deprecation_error do
302 shell_error("Migration is not allowed until all deprecation warnings have been resolved.")
303 end
304
305 if Code.ensure_loaded?(Config.Reader) do
306 defp config_header, do: "import Config\r\n\r\n"
307 defp read_file(config_file), do: Config.Reader.read_imports!(config_file)
308 else
309 defp config_header, do: "use Mix.Config\r\n\r\n"
310 defp read_file(config_file), do: Mix.Config.eval!(config_file)
311 end
312
313 defp write_and_delete(config, file, delete?) do
314 config
315 |> write(file)
316 |> delete(delete?)
317 end
318
319 defp write(config, file) do
320 value = inspect(config.value, limit: :infinity)
321
322 IO.write(file, "config #{inspect(config.group)}, #{inspect(config.key)}, #{value}\r\n\r\n")
323
324 config
325 end
326
327 defp delete(config, true) do
328 {:ok, _} = Repo.delete(config)
329
330 shell_info(
331 "config #{inspect(config.group)}, #{inspect(config.key)} deleted from the ConfigDB."
332 )
333 end
334
335 defp delete(_config, _), do: :ok
336
337 defp dump(%Pleroma.ConfigDB{} = config) do
338 value = inspect(config.value, limit: :infinity)
339
340 shell_info("config #{inspect(config.group)}, #{inspect(config.key)}, #{value}\r\n\r\n")
341 end
342
343 defp configdb_not_enabled do
344 shell_error(
345 "ConfigDB not enabled. Please check the value of :configurable_from_database in your configuration."
346 )
347 end
348
349 defp dump_key(group, key) when is_atom(group) and is_atom(key) do
350 ConfigDB
351 |> Repo.all()
352 |> Enum.filter(fn x ->
353 if x.group == group && x.key == key do
354 x |> dump
355 end
356 end)
357 end
358
359 defp dump_group(group) when is_atom(group) do
360 ConfigDB
361 |> Repo.all()
362 |> Enum.filter(fn x ->
363 if x.group == group do
364 x |> dump
365 end
366 end)
367 end
368
369 defp group_exists?(group) when is_atom(group) do
370 result =
371 ConfigDB
372 |> Repo.all()
373 |> Enum.filter(fn x ->
374 if x.group == group do
375 x
376 end
377 end)
378
379 unless result == [] do
380 true
381 else
382 false
383 end
384 end
385
386 def maybe_atomize(arg) when is_atom(arg), do: arg
387
388 def maybe_atomize(arg) when is_binary(arg) do
389 chars = String.codepoints(arg)
390
391 if "." in chars do
392 :"Elixir.#{arg}"
393 else
394 String.to_atom(arg)
395 end
396 end
397 end