From d7ec0898e5aa7acae463760fd85d1ebf8307b4f9 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sat, 8 Jun 2019 17:40:40 +0300 Subject: [PATCH] Make mix tasks work in a release --- lib/mix/tasks/pleroma/common.ex | 54 ++++++++++++++++++++------ lib/mix/tasks/pleroma/instance.ex | 12 +++--- lib/mix/tasks/pleroma/relay.ex | 4 +- lib/mix/tasks/pleroma/uploads.ex | 12 +++--- lib/mix/tasks/pleroma/user.ex | 64 +++++++++++++++---------------- lib/pleroma/release_tasks.ex | 38 ++++++++++++++++++ rel/pleroma_ctl | 2 +- 7 files changed, 128 insertions(+), 58 deletions(-) create mode 100644 lib/pleroma/release_tasks.ex diff --git a/lib/mix/tasks/pleroma/common.ex b/lib/mix/tasks/pleroma/common.ex index 25977f656..0e03a7872 100644 --- a/lib/mix/tasks/pleroma/common.ex +++ b/lib/mix/tasks/pleroma/common.ex @@ -10,19 +10,51 @@ def start_pleroma do end def get_option(options, opt, prompt, defval \\ nil, defname \\ nil) do - Keyword.get(options, opt) || - case Mix.shell().prompt("#{prompt} [#{defname || defval}]") do - "\n" -> - case defval do - nil -> get_option(options, opt, prompt, defval) - defval -> defval - end - - opt -> - opt |> String.trim() - end + Keyword.get(options, opt) || shell_prompt(prompt, defval, defname) end + def shell_prompt(prompt, defval \\ nil, defname \\ nil) do + prompt_message = "#{prompt} [#{defname || defval}]" + + input = + if mix_shell?(), + do: Mix.shell().prompt(prompt_message), + else: :io.get_line(prompt_message) + + case input do + "\n" -> + case defval do + nil -> + shell_prompt(prompt, defval, defname) + + defval -> + defval + end + + input -> + String.trim(input) + end + end + + def shell_yes?(message) do + shell_prompt(message, "Yn") in ~w(Yn Y y) + end + + def shell_info(message) do + if mix_shell?(), + do: Mix.shell().info(message), + else: IO.puts(message) + end + + def shell_error(message) do + if mix_shell?(), + do: Mix.shell().error(message), + else: IO.puts(:stderr, message) + end + + @doc "Performs a safe check whether `Mix.shell/0` is available (does not raise if Mix is not loaded)" + def mix_shell?, do: :erlang.function_exported(Mix, :shell, 0) + def escape_sh_path(path) do ~S(') <> String.replace(path, ~S('), ~S(\')) <> ~S(') end diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex index 6cee8d630..88925dbaf 100644 --- a/lib/mix/tasks/pleroma/instance.ex +++ b/lib/mix/tasks/pleroma/instance.ex @@ -155,17 +155,17 @@ def run(["gen" | rest]) do dbpass: dbpass ) - Mix.shell().info( + Common.shell_info( "Writing config to #{config_path}. You should rename it to config/prod.secret.exs or config/dev.secret.exs." ) File.write(config_path, result_config) - Mix.shell().info("Writing #{psql_path}.") + Common.shell_info("Writing #{psql_path}.") File.write(psql_path, result_psql) write_robots_txt(indexable) - Mix.shell().info( + Common.shell_info( "\n" <> """ To get started: @@ -179,7 +179,7 @@ def run(["gen" | rest]) do end ) else - Mix.shell().error( + Common.shell_error( "The task would have overwritten the following files:\n" <> (Enum.map(paths, &"- #{&1}\n") |> Enum.join("")) <> "Rerun with `--force` to overwrite them." @@ -204,10 +204,10 @@ defp write_robots_txt(indexable) do if File.exists?(robots_txt_path) do File.cp!(robots_txt_path, "#{robots_txt_path}.bak") - Mix.shell().info("Backing up existing robots.txt to #{robots_txt_path}.bak") + Common.shell_info("Backing up existing robots.txt to #{robots_txt_path}.bak") end File.write(robots_txt_path, robots_txt) - Mix.shell().info("Writing #{robots_txt_path}.") + Common.shell_info("Writing #{robots_txt_path}.") end end diff --git a/lib/mix/tasks/pleroma/relay.ex b/lib/mix/tasks/pleroma/relay.ex index fbec473c5..213ae24d2 100644 --- a/lib/mix/tasks/pleroma/relay.ex +++ b/lib/mix/tasks/pleroma/relay.ex @@ -30,7 +30,7 @@ def run(["follow", target]) do # put this task to sleep to allow the genserver to push out the messages :timer.sleep(500) else - {:error, e} -> Mix.shell().error("Error while following #{target}: #{inspect(e)}") + {:error, e} -> Common.shell_error("Error while following #{target}: #{inspect(e)}") end end @@ -41,7 +41,7 @@ def run(["unfollow", target]) do # put this task to sleep to allow the genserver to push out the messages :timer.sleep(500) else - {:error, e} -> Mix.shell().error("Error while following #{target}: #{inspect(e)}") + {:error, e} -> Common.shell_error("Error while following #{target}: #{inspect(e)}") end end end diff --git a/lib/mix/tasks/pleroma/uploads.ex b/lib/mix/tasks/pleroma/uploads.ex index 106fcf443..8855b5538 100644 --- a/lib/mix/tasks/pleroma/uploads.ex +++ b/lib/mix/tasks/pleroma/uploads.ex @@ -38,10 +38,10 @@ def run(["migrate_local", target_uploader | args]) do Pleroma.Config.put([Upload, :uploader], uploader) end - Mix.shell().info("Migrating files from local #{local_path} to #{to_string(uploader)}") + Common.shell_info("Migrating files from local #{local_path} to #{to_string(uploader)}") if delete? do - Mix.shell().info( + Common.shell_info( "Attention: uploaded files will be deleted, hope you have backups! (--delete ; cancel with ^C)" ) @@ -78,7 +78,7 @@ def run(["migrate_local", target_uploader | args]) do |> Enum.filter(& &1) total_count = length(uploads) - Mix.shell().info("Found #{total_count} uploads") + Common.shell_info("Found #{total_count} uploads") uploads |> Task.async_stream( @@ -90,7 +90,7 @@ def run(["migrate_local", target_uploader | args]) do :ok error -> - Mix.shell().error("failed to upload #{inspect(upload.path)}: #{inspect(error)}") + Common.shell_error("failed to upload #{inspect(upload.path)}: #{inspect(error)}") end end, timeout: 150_000 @@ -99,10 +99,10 @@ def run(["migrate_local", target_uploader | args]) do # credo:disable-for-next-line Credo.Check.Warning.UnusedEnumOperation |> Enum.reduce(0, fn done, count -> count = count + length(done) - Mix.shell().info("Uploaded #{count}/#{total_count} files") + Common.shell_info("Uploaded #{count}/#{total_count} files") count end) - Mix.shell().info("Done!") + Common.shell_info("Done!") end end diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index 25fc40ea7..7eaa49836 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -115,7 +115,7 @@ def run(["new", nickname, email | rest]) do admin? = Keyword.get(options, :admin, false) assume_yes? = Keyword.get(options, :assume_yes, false) - Mix.shell().info(""" + Common.shell_info(""" A user will be created with the following information: - nickname: #{nickname} - email: #{email} @@ -128,7 +128,7 @@ def run(["new", nickname, email | rest]) do - admin: #{if(admin?, do: "true", else: "false")} """) - proceed? = assume_yes? or Mix.shell().yes?("Continue?") + proceed? = assume_yes? or Common.shell_yes?("Continue?") if proceed? do Common.start_pleroma() @@ -145,7 +145,7 @@ def run(["new", nickname, email | rest]) do changeset = User.register_changeset(%User{}, params, need_confirmation: false) {:ok, _user} = User.register(changeset) - Mix.shell().info("User #{nickname} created") + Common.shell_info("User #{nickname} created") if moderator? do run(["set", nickname, "--moderator"]) @@ -159,7 +159,7 @@ def run(["new", nickname, email | rest]) do run(["reset_password", nickname]) end else - Mix.shell().info("User will not be created.") + Common.shell_info("User will not be created.") end end @@ -168,10 +168,10 @@ def run(["rm", nickname]) do with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do User.perform(:delete, user) - Mix.shell().info("User #{nickname} deleted.") + Common.shell_info("User #{nickname} deleted.") else _ -> - Mix.shell().error("No local user #{nickname}") + Common.shell_error("No local user #{nickname}") end end @@ -181,12 +181,12 @@ def run(["toggle_activated", nickname]) do with %User{} = user <- User.get_cached_by_nickname(nickname) do {:ok, user} = User.deactivate(user, !user.info.deactivated) - Mix.shell().info( + Common.shell_info( "Activation status of #{nickname}: #{if(user.info.deactivated, do: "de", else: "")}activated" ) else _ -> - Mix.shell().error("No user #{nickname}") + Common.shell_error("No user #{nickname}") end end @@ -195,7 +195,7 @@ def run(["reset_password", nickname]) do with %User{local: true} = user <- User.get_cached_by_nickname(nickname), {:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do - Mix.shell().info("Generated password reset token for #{user.nickname}") + Common.shell_info("Generated password reset token for #{user.nickname}") IO.puts( "URL: #{ @@ -208,7 +208,7 @@ def run(["reset_password", nickname]) do ) else _ -> - Mix.shell().error("No local user #{nickname}") + Common.shell_error("No local user #{nickname}") end end @@ -216,7 +216,7 @@ def run(["unsubscribe", nickname]) do Common.start_pleroma() with %User{} = user <- User.get_cached_by_nickname(nickname) do - Mix.shell().info("Deactivating #{user.nickname}") + Common.shell_info("Deactivating #{user.nickname}") User.deactivate(user) {:ok, friends} = User.get_friends(user) @@ -224,7 +224,7 @@ def run(["unsubscribe", nickname]) do Enum.each(friends, fn friend -> user = User.get_cached_by_id(user.id) - Mix.shell().info("Unsubscribing #{friend.nickname} from #{user.nickname}") + Common.shell_info("Unsubscribing #{friend.nickname} from #{user.nickname}") User.unfollow(user, friend) end) @@ -233,11 +233,11 @@ def run(["unsubscribe", nickname]) do user = User.get_cached_by_id(user.id) if Enum.empty?(user.following) do - Mix.shell().info("Successfully unsubscribed all followers from #{user.nickname}") + Common.shell_info("Successfully unsubscribed all followers from #{user.nickname}") end else _ -> - Mix.shell().error("No user #{nickname}") + Common.shell_error("No user #{nickname}") end end @@ -274,7 +274,7 @@ def run(["set", nickname | rest]) do end else _ -> - Mix.shell().error("No local user #{nickname}") + Common.shell_error("No local user #{nickname}") end end @@ -284,10 +284,10 @@ def run(["tag", nickname | tags]) do with %User{} = user <- User.get_cached_by_nickname(nickname) do user = user |> User.tag(tags) - Mix.shell().info("Tags of #{user.nickname}: #{inspect(tags)}") + Common.shell_info("Tags of #{user.nickname}: #{inspect(tags)}") else _ -> - Mix.shell().error("Could not change user tags for #{nickname}") + Common.shell_error("Could not change user tags for #{nickname}") end end @@ -297,10 +297,10 @@ def run(["untag", nickname | tags]) do with %User{} = user <- User.get_cached_by_nickname(nickname) do user = user |> User.untag(tags) - Mix.shell().info("Tags of #{user.nickname}: #{inspect(tags)}") + Common.shell_info("Tags of #{user.nickname}: #{inspect(tags)}") else _ -> - Mix.shell().error("Could not change user tags for #{nickname}") + Common.shell_error("Could not change user tags for #{nickname}") end end @@ -326,7 +326,7 @@ def run(["invite" | rest]) do with {:ok, val} <- options[:expires_at], options = Map.put(options, :expires_at, val), {:ok, invite} <- UserInviteToken.create_invite(options) do - Mix.shell().info( + Common.shell_info( "Generated user invite token " <> String.replace(invite.invite_type, "_", " ") ) @@ -340,14 +340,14 @@ def run(["invite" | rest]) do IO.puts(url) else error -> - Mix.shell().error("Could not create invite token: #{inspect(error)}") + Common.shell_error("Could not create invite token: #{inspect(error)}") end end def run(["invites"]) do Common.start_pleroma() - Mix.shell().info("Invites list:") + Common.shell_info("Invites list:") UserInviteToken.list_invites() |> Enum.each(fn invite -> @@ -361,7 +361,7 @@ def run(["invites"]) do " | Max use: #{max_use} Left use: #{max_use - invite.uses}" end - Mix.shell().info( + Common.shell_info( "ID: #{invite.id} | Token: #{invite.token} | Token type: #{invite.invite_type} | Used: #{ invite.used }#{expire_info}#{using_info}" @@ -374,9 +374,9 @@ def run(["revoke_invite", token]) do with {:ok, invite} <- UserInviteToken.find_by_token(token), {:ok, _} <- UserInviteToken.update_invite(invite, %{used: true}) do - Mix.shell().info("Invite for token #{token} was revoked.") + Common.shell_info("Invite for token #{token} was revoked.") else - _ -> Mix.shell().error("No invite found with token #{token}") + _ -> Common.shell_error("No invite found with token #{token}") end end @@ -385,10 +385,10 @@ def run(["delete_activities", nickname]) do with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do {:ok, _} = User.delete_user_activities(user) - Mix.shell().info("User #{nickname} statuses deleted.") + Common.shell_info("User #{nickname} statuses deleted.") else _ -> - Mix.shell().error("No local user #{nickname}") + Common.shell_error("No local user #{nickname}") end end @@ -400,10 +400,10 @@ def run(["toggle_confirmed", nickname]) do message = if user.info.confirmation_pending, do: "needs", else: "doesn't need" - Mix.shell().info("#{nickname} #{message} confirmation.") + Common.shell_info("#{nickname} #{message} confirmation.") else _ -> - Mix.shell().error("No local user #{nickname}") + Common.shell_error("No local user #{nickname}") end end @@ -416,7 +416,7 @@ defp set_moderator(user, value) do {:ok, user} = User.update_and_set_cache(user_cng) - Mix.shell().info("Moderator status of #{user.nickname}: #{user.info.is_moderator}") + Common.shell_info("Moderator status of #{user.nickname}: #{user.info.is_moderator}") user end @@ -429,7 +429,7 @@ defp set_admin(user, value) do {:ok, user} = User.update_and_set_cache(user_cng) - Mix.shell().info("Admin status of #{user.nickname}: #{user.info.is_admin}") + Common.shell_info("Admin status of #{user.nickname}: #{user.info.is_admin}") user end @@ -442,7 +442,7 @@ defp set_locked(user, value) do {:ok, user} = User.update_and_set_cache(user_cng) - Mix.shell().info("Locked status of #{user.nickname}: #{user.info.locked}") + Common.shell_info("Locked status of #{user.nickname}: #{user.info.locked}") user end end diff --git a/lib/pleroma/release_tasks.ex b/lib/pleroma/release_tasks.ex new file mode 100644 index 000000000..66a8b627f --- /dev/null +++ b/lib/pleroma/release_tasks.ex @@ -0,0 +1,38 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.ReleaseTasks do + def run(args) do + [task | args] = String.split(args) + + case task do + "migrate" -> migrate(args) + task -> mix_task(task, args) + end + end + + defp mix_task(task, args) do + # Modules are not loaded before application starts + Mix.Tasks.Pleroma.Common.start_pleroma() + {:ok, modules} = :application.get_key(:pleroma, :modules) + + module = + Enum.find(modules, fn module -> + module = Module.split(module) + + match?(["Mix", "Tasks", "Pleroma" | _], module) and + String.downcase(List.last(module)) == task + end) + + if module do + module.run(args) + else + IO.puts("The task #{task} does not exist") + end + end + + defp migrate(_args) do + :noop + end +end diff --git a/rel/pleroma_ctl b/rel/pleroma_ctl index 543b742b9..16526af44 100755 --- a/rel/pleroma_ctl +++ b/rel/pleroma_ctl @@ -2,4 +2,4 @@ # XXX: This should be removed when elixir's releases get custom command support SCRIPT=$(readlink -f "$0") SCRIPTPATH=$(dirname "$SCRIPT") -$SCRIPTPATH/pleroma eval 'Pleroma.ReleaseTasks.mix_task("'"$*"'")' +$SCRIPTPATH/pleroma eval 'Pleroma.ReleaseTasks.run("'"$*"'")'