instance.ex 10.5 KB
Newer Older
kaniini's avatar
kaniini committed
1
# Pleroma: A lightweight social networking server
Sean King's avatar
Sean King committed
2
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
kaniini's avatar
kaniini committed
3
4
# SPDX-License-Identifier: AGPL-3.0-only

Rin Toshaka's avatar
Rin Toshaka committed
5
defmodule Mix.Tasks.Pleroma.Instance do
Jorty's avatar
Jorty committed
6
  use Mix.Task
7
  import Mix.Pleroma
Jorty's avatar
Jorty committed
8

9
10
  alias Pleroma.Config

Rin Toshaka's avatar
Rin Toshaka committed
11
  @shortdoc "Manages Pleroma instance"
12
  @moduledoc File.read!("docs/administration/CLI_tasks/instance.md")
Jorty's avatar
Jorty committed
13

14
  def run(["gen" | rest]) do
Jorty's avatar
Jorty committed
15
16
17
18
19
20
21
22
23
24
    {options, [], []} =
      OptionParser.parse(
        rest,
        strict: [
          force: :boolean,
          output: :string,
          output_psql: :string,
          domain: :string,
          instance_name: :string,
          admin_email: :string,
25
          notify_email: :string,
Jorty's avatar
Jorty committed
26
27
28
          dbhost: :string,
          dbname: :string,
          dbuser: :string,
29
          dbpass: :string,
30
          rum: :string,
31
          indexable: :string,
32
33
          db_configurable: :string,
          uploads_dir: :string,
34
35
          static_dir: :string,
          listen_ip: :string,
36
          listen_port: :string,
Ilja's avatar
Ilja committed
37
          strip_uploads_location: :string,
Ilja's avatar
Ilja committed
38
          read_uploads_description: :string,
39
          anonymize_uploads: :string,
rinpatch's avatar
rinpatch committed
40
          dedupe_uploads: :string
Jorty's avatar
Jorty committed
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
        ],
        aliases: [
          o: :output,
          f: :force
        ]
      )

    paths =
      [config_path, psql_path] = [
        Keyword.get(options, :output, "config/generated_config.exs"),
        Keyword.get(options, :output_psql, "config/setup_db.psql")
      ]

    will_overwrite = Enum.filter(paths, &File.exists?/1)
    proceed? = Enum.empty?(will_overwrite) or Keyword.get(options, :force, false)

57
    if proceed? do
58
59
      [domain, port | _] =
        String.split(
60
          get_option(
61
62
63
64
65
66
            options,
            :domain,
            "What domain will your instance use? (e.g pleroma.soykaf.com)"
          ),
          ":"
        ) ++ [443]
Jorty's avatar
Jorty committed
67
68

      name =
69
        get_option(
70
          options,
71
          :instance_name,
72
          "What is the name of your instance? (e.g. The Corndog Emporium)",
73
          domain
74
        )
Jorty's avatar
Jorty committed
75

76
      email = get_option(options, :admin_email, "What is your admin email address?")
77

78
      notify_email =
79
        get_option(
80
81
82
83
84
85
          options,
          :notify_email,
          "What email address do you want to use for sending email notifications?",
          email
        )

86
      indexable =
87
        get_option(
88
89
90
91
92
93
          options,
          :indexable,
          "Do you want search engines to index your site? (y/n)",
          "y"
        ) === "y"

94
      db_configurable? =
95
        get_option(
96
97
          options,
          :db_configurable,
98
          "Do you want to store the configuration in the database (allows controlling it from admin-fe)? (y/n)",
99
          "n"
100
        ) === "y"
101

102
      dbhost = get_option(options, :dbhost, "What is the hostname of your database?", "localhost")
103

104
      dbname = get_option(options, :dbname, "What is the name of your database?", "pleroma")
Jorty's avatar
Jorty committed
105
106

      dbuser =
107
        get_option(
108
109
110
111
112
          options,
          :dbuser,
          "What is the user used to connect to your database?",
          "pleroma"
        )
Jorty's avatar
Jorty committed
113
114

      dbpass =
115
        get_option(
116
117
118
119
120
121
          options,
          :dbpass,
          "What is the password used to connect to your database?",
          :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64),
          "autogenerated"
        )
Jorty's avatar
Jorty committed
122

123
124
125
126
127
128
129
130
      rum_enabled =
        get_option(
          options,
          :rum,
          "Would you like to use RUM indices?",
          "n"
        ) === "y"

131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
      listen_port =
        get_option(
          options,
          :listen_port,
          "What port will the app listen to (leave it if you are using the default setup with nginx)?",
          4000
        )

      listen_ip =
        get_option(
          options,
          :listen_ip,
          "What ip will the app listen to (leave it if you are using the default setup with nginx)?",
          "127.0.0.1"
        )

147
148
149
      uploads_dir =
        get_option(
          options,
150
          :uploads_dir,
151
          "What directory should media uploads go in (when using the local uploader)?",
feld's avatar
feld committed
152
          Config.get([Pleroma.Uploaders.Local, :uploads])
153
        )
Maksim's avatar
Maksim committed
154
        |> Path.expand()
155
156
157
158
159
160

      static_dir =
        get_option(
          options,
          :static_dir,
          "What directory should custom public files be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)?",
feld's avatar
feld committed
161
          Config.get([:instance, :static_dir])
162
        )
Maksim's avatar
Maksim committed
163
        |> Path.expand()
164

Ilja's avatar
Ilja committed
165
      {strip_uploads_location_message, strip_uploads_location_default} =
166
167
168
169
170
171
172
173
        if Pleroma.Utils.command_available?("exiftool") do
          {"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as installed. (y/n)",
           "y"}
        else
          {"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as not installed, please install it if you answer yes. (y/n)",
           "n"}
        end

Ilja's avatar
Ilja committed
174
      strip_uploads_location =
175
176
        get_option(
          options,
Ilja's avatar
Ilja committed
177
178
179
          :strip_uploads_location,
          strip_uploads_location_message,
          strip_uploads_location_default
180
181
        ) === "y"

Ilja's avatar
Ilja committed
182
      {read_uploads_description_message, read_uploads_description_default} =
183
184
185
186
187
188
189
190
        if Pleroma.Utils.command_available?("exiftool") do
          {"Do you want to read data from uploaded files so clients can use it to prefill fields like image description? This requires exiftool, it was detected as installed. (y/n)",
           "y"}
        else
          {"Do you want to read data from uploaded files so clients can use it to prefill fields like image description? This requires exiftool, it was detected as not installed, please install it if you answer yes. (y/n)",
           "n"}
        end

Ilja's avatar
Ilja committed
191
      read_uploads_description =
192
193
        get_option(
          options,
Ilja's avatar
Ilja committed
194
195
196
          :read_uploads_description,
          read_uploads_description_message,
          read_uploads_description_default
197
198
        ) === "y"

199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
      anonymize_uploads =
        get_option(
          options,
          :anonymize_uploads,
          "Do you want to anonymize the filenames of uploads? (y/n)",
          "n"
        ) === "y"

      dedupe_uploads =
        get_option(
          options,
          :dedupe_uploads,
          "Do you want to deduplicate uploaded files? (y/n)",
          "n"
        ) === "y"

215
216
      Config.put([:instance, :static_dir], static_dir)

Jorty's avatar
Jorty committed
217
      secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
Roman Chvanikov's avatar
Roman Chvanikov committed
218
      jwt_secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
219
      signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
Alex Gleason's avatar
Alex Gleason committed
220
      lv_signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
Rin Toshaka's avatar
Rin Toshaka committed
221
      {web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1)
222
      template_dir = Application.app_dir(:pleroma, "priv") <> "/templates"
Jorty's avatar
Jorty committed
223
224
225

      result_config =
        EEx.eval_file(
226
          template_dir <> "/sample_config.eex",
Jorty's avatar
Jorty committed
227
          domain: domain,
228
          port: port,
Jorty's avatar
Jorty committed
229
          email: email,
230
          notify_email: notify_email,
Jorty's avatar
Jorty committed
231
232
233
234
235
          name: name,
          dbhost: dbhost,
          dbname: dbname,
          dbuser: dbuser,
          dbpass: dbpass,
Rin Toshaka's avatar
Rin Toshaka committed
236
          secret: secret,
Roman Chvanikov's avatar
Roman Chvanikov committed
237
          jwt_secret: jwt_secret,
238
          signing_salt: signing_salt,
Alex Gleason's avatar
Alex Gleason committed
239
          lv_signing_salt: lv_signing_salt,
Rin Toshaka's avatar
Rin Toshaka committed
240
          web_push_public_key: Base.url_encode64(web_push_public_key, padding: false),
241
          web_push_private_key: Base.url_encode64(web_push_private_key, padding: false),
242
243
          db_configurable?: db_configurable?,
          static_dir: static_dir,
244
          uploads_dir: uploads_dir,
245
246
          rum_enabled: rum_enabled,
          listen_ip: listen_ip,
247
248
249
          listen_port: listen_port,
          upload_filters:
            upload_filters(%{
Ilja's avatar
Ilja committed
250
              strip_location: strip_uploads_location,
Ilja's avatar
Ilja committed
251
              read_description: read_uploads_description,
252
253
254
              anonymize: anonymize_uploads,
              dedupe: dedupe_uploads
            })
Jorty's avatar
Jorty committed
255
256
257
258
        )

      result_psql =
        EEx.eval_file(
259
          template_dir <> "/sample_psql.eex",
Jorty's avatar
Jorty committed
260
261
          dbname: dbname,
          dbuser: dbuser,
262
263
          dbpass: dbpass,
          rum_enabled: rum_enabled
Jorty's avatar
Jorty committed
264
265
        )

266
267
      config_dir = Path.dirname(config_path)
      psql_dir = Path.dirname(psql_path)
a1batross's avatar
a1batross committed
268

269
270
271
272
      [config_dir, psql_dir, static_dir, uploads_dir]
      |> Enum.reject(&File.exists?/1)
      |> Enum.map(&File.mkdir_p!/1)

273
      shell_info("Writing config to #{config_path}.")
Jorty's avatar
Jorty committed
274
275

      File.write(config_path, result_config)
276
      shell_info("Writing the postgres script to #{psql_path}.")
Jorty's avatar
Jorty committed
277
278
      File.write(psql_path, result_psql)

Maksim's avatar
Maksim committed
279
      write_robots_txt(static_dir, indexable, template_dir)
280

281
      shell_info(
282
        "\n All files successfully written! Refer to the installation instructions for your platform for next steps."
Jorty's avatar
Jorty committed
283
      )
284
285
286
287
288
289

      if db_configurable? do
        shell_info(
          " Please transfer your config to the database after running database migrations. Refer to \"Transfering the config to/from the database\" section of the docs for more information."
        )
      end
Jorty's avatar
Jorty committed
290
    else
291
      shell_error(
Jorty's avatar
Jorty committed
292
        "The task would have overwritten the following files:\n" <>
293
          (Enum.map(will_overwrite, &"- #{&1}\n") |> Enum.join("")) <>
Jorty's avatar
Jorty committed
294
295
296
297
          "Rerun with `--force` to overwrite them."
      )
    end
  end
298

Maksim's avatar
Maksim committed
299
  defp write_robots_txt(static_dir, indexable, template_dir) do
300
301
    robots_txt =
      EEx.eval_file(
302
        template_dir <> "/robots_txt.eex",
303
304
305
306
307
308
309
        indexable: indexable
      )

    robots_txt_path = Path.join(static_dir, "robots.txt")

    if File.exists?(robots_txt_path) do
      File.cp!(robots_txt_path, "#{robots_txt_path}.bak")
310
      shell_info("Backing up existing robots.txt to #{robots_txt_path}.bak")
311
312
313
    end

    File.write(robots_txt_path, robots_txt)
314
    shell_info("Writing #{robots_txt_path}.")
315
  end
316
317
318

  defp upload_filters(filters) when is_map(filters) do
    enabled_filters =
Ilja's avatar
Ilja committed
319
320
      if filters.strip_location do
        [Pleroma.Upload.Filter.Exiftool.StripLocation]
321
322
323
324
      else
        []
      end

325
    enabled_filters =
Ilja's avatar
Ilja committed
326
327
      if filters.read_description do
        enabled_filters ++ [Pleroma.Upload.Filter.Exiftool.ReadDescription]
328
329
330
331
      else
        enabled_filters
      end

332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
    enabled_filters =
      if filters.anonymize do
        enabled_filters ++ [Pleroma.Upload.Filter.AnonymizeFilename]
      else
        enabled_filters
      end

    enabled_filters =
      if filters.dedupe do
        enabled_filters ++ [Pleroma.Upload.Filter.Dedupe]
      else
        enabled_filters
      end

    enabled_filters
  end

  defp upload_filters(_), do: []
Jorty's avatar
Jorty committed
350
end