forked from potsda.mn/mobilizon
Merge branch 'Pascoual/mobilizon-docker-multi-stage-prod' into 'master'
Docker support See merge request framasoft/mobilizon!674
This commit is contained in:
commit
c0591567f4
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -38,3 +38,4 @@ release/
|
|||
*.mo
|
||||
*.po~
|
||||
.weblate
|
||||
docker/production/.env
|
||||
|
|
42
docker/production/Dockerfile
Normal file
42
docker/production/Dockerfile
Normal file
|
@ -0,0 +1,42 @@
|
|||
# First build the application assets
|
||||
FROM node:alpine as assets
|
||||
|
||||
RUN apk add --no-cache python build-base
|
||||
|
||||
COPY js .
|
||||
RUN yarn install \
|
||||
&& yarn run build
|
||||
|
||||
# Then, build the application binary
|
||||
FROM elixir:alpine AS builder
|
||||
|
||||
RUN apk add --no-cache build-base git cmake
|
||||
|
||||
COPY mix.exs mix.lock ./
|
||||
ENV MIX_ENV=prod
|
||||
RUN mix local.hex --force \
|
||||
&& mix local.rebar --force \
|
||||
&& mix deps.get
|
||||
|
||||
COPY lib ./lib
|
||||
COPY priv ./priv
|
||||
COPY config ./config
|
||||
COPY rel ./rel
|
||||
COPY docker/production/releases.exs ./config/
|
||||
COPY --from=assets ./priv/static ./priv/static
|
||||
|
||||
RUN mix phx.digest \
|
||||
&& mix release
|
||||
|
||||
# Finally setup the app
|
||||
FROM alpine
|
||||
|
||||
RUN apk add --no-cache openssl ncurses-libs file
|
||||
|
||||
USER nobody
|
||||
EXPOSE 4000
|
||||
|
||||
COPY --from=builder --chown=nobody:nobody _build/prod/rel/mobilizon ./
|
||||
COPY docker/production/docker-entrypoint.sh ./
|
||||
|
||||
ENTRYPOINT ["./docker-entrypoint.sh"]
|
42
docker/production/docker-compose.yml
Normal file
42
docker/production/docker-compose.yml
Normal file
|
@ -0,0 +1,42 @@
|
|||
version: "3"
|
||||
|
||||
services:
|
||||
mobilizon:
|
||||
image: framasoft/mobilizon
|
||||
environment:
|
||||
- MOBILIZON_INSTANCE_NAME
|
||||
- MOBILIZON_INSTANCE_HOST
|
||||
- MOBILIZON_INSTANCE_EMAIL
|
||||
- MOBILIZON_REPLY_EMAIL
|
||||
- MOBILIZON_ADMIN_EMAIL
|
||||
- MOBILIZON_INSTANCE_REGISTRATIONS_OPEN
|
||||
- MOBILIZON_DATABASE_USERNAME=${POSTGRES_USER}
|
||||
- MOBILIZON_DATABASE_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- MOBILIZON_DATABASE_DBNAME=${POSTGRES_DB}
|
||||
- MOBILIZON_DATABASE_HOST=db
|
||||
- MOBILIZON_INSTANCE_SECRET_KEY_BASE
|
||||
- MOBILIZON_INSTANCE_SECRET_KEY
|
||||
- MOBILIZON_SMTP_SERVER
|
||||
- MOBILIZON_SMTP_HOSTNAME
|
||||
- MOBILIZON_SMTP_PORT
|
||||
- MOBILIZON_SMTP_SSL
|
||||
- MOBILIZON_SMTP_USERNAME
|
||||
- MOBILIZON_SMTP_PASSWORD
|
||||
volumes:
|
||||
- ./public/uploads:/app/uploads
|
||||
ports:
|
||||
- "4000:4000"
|
||||
|
||||
db:
|
||||
image: postgis/postgis
|
||||
volumes:
|
||||
- ./db:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_USER
|
||||
- POSTGRES_PASSWORD
|
||||
- POSTGRES_DB
|
||||
|
||||
networks:
|
||||
default:
|
||||
ipam:
|
||||
driver: default
|
9
docker/production/docker-entrypoint.sh
Executable file
9
docker/production/docker-entrypoint.sh
Executable file
|
@ -0,0 +1,9 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
echo "-- Running migrations..."
|
||||
/bin/mobilizon_ctl migrate
|
||||
|
||||
echo "-- Starting!"
|
||||
exec /bin/mobilizon start
|
20
docker/production/env.template
Normal file
20
docker/production/env.template
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Copy this file to .env, then update it with your own settings
|
||||
|
||||
# Database settings
|
||||
POSTGRES_USER=mobilizon
|
||||
POSTGRES_PASSWORD=changethis
|
||||
POSTGRES_DB=mobilizon
|
||||
|
||||
# Instance configuration
|
||||
MOBILIZON_INSTANCE_NAME=My Mobilizon Instance
|
||||
MOBILIZON_INSTANCE_HOST=mobilizon.lan
|
||||
MOBILIZON_INSTANCE_SECRET_KEY_BASE=changethis
|
||||
MOBILIZON_INSTANCE_SECRET_KEY=changethis
|
||||
MOBILIZON_INSTANCE_EMAIL=noreply@mobilizon.lan
|
||||
MOBILIZON_REPLY_EMAIL=contact@mobilizon.lan
|
||||
|
||||
# Email settings
|
||||
MOBILIZON_SMTP_SERVER=localhost
|
||||
MOBILIZON_SMTP_HOSTNAME=localhost
|
||||
MOBILIZON_SMTP_USERNAME=noreply@mobilizon.lan
|
||||
MOBILIZON_SMTP_PASSWORD=password
|
51
docker/production/releases.exs
Normal file
51
docker/production/releases.exs
Normal file
|
@ -0,0 +1,51 @@
|
|||
# Mobilizon instance configuration
|
||||
|
||||
import Config
|
||||
|
||||
config :mobilizon, Mobilizon.Web.Endpoint,
|
||||
server: true,
|
||||
url: [host: System.get_env("MOBILIZON_INSTANCE_HOST", "mobilizon.lan")],
|
||||
http: [port: 4000],
|
||||
secret_key_base: System.get_env("MOBILIZON_INSTANCE_SECRET_KEY_BASE", "changethis")
|
||||
|
||||
config :mobilizon, Mobilizon.Web.Auth.Guardian,
|
||||
secret_key: System.get_env("MOBILIZON_INSTANCE_SECRET_KEY", "changethis")
|
||||
|
||||
config :mobilizon, :instance,
|
||||
name: System.get_env("MOBILIZON_INSTANCE_NAME", "Mobilizon"),
|
||||
description: "Change this to a proper description of your instance",
|
||||
hostname: System.get_env("MOBILIZON_INSTANCE_HOST", "mobilizon.lan"),
|
||||
registrations_open: System.get_env("MOBILIZON_INSTANCE_REGISTRATIONS_OPEN", "false") == "true",
|
||||
demo: false,
|
||||
allow_relay: true,
|
||||
federating: true,
|
||||
email_from: System.get_env("MOBILIZON_INSTANCE_EMAIL", "noreply@mobilizon.lan"),
|
||||
email_reply_to: System.get_env("MOBILIZON_REPLY_EMAIL", "noreply@mobilizon.lan")
|
||||
|
||||
|
||||
config :mobilizon, Mobilizon.Web.Upload.Uploader.Local,
|
||||
uploads: System.get_env("MOBILIZON_UPLOADS", "/app/uploads")
|
||||
|
||||
|
||||
config :mobilizon, Mobilizon.Storage.Repo,
|
||||
adapter: Ecto.Adapters.Postgres,
|
||||
username: System.get_env("MOBILIZON_DATABASE_USERNAME", "username"),
|
||||
password: System.get_env("MOBILIZON_DATABASE_PASSWORD", "password"),
|
||||
database: System.get_env("MOBILIZON_DATABASE_DBNAME", "mobilizon"),
|
||||
hostname: System.get_env("MOBILIZON_DATABASE_HOST", "postgres"),
|
||||
port: 5432,
|
||||
pool_size: 10
|
||||
|
||||
config :mobilizon, Mobilizon.Web.Email.Mailer,
|
||||
adapter: Bamboo.SMTPAdapter,
|
||||
server: System.get_env("MOBILIZON_SMTP_SERVER", "localhost"),
|
||||
hostname: System.get_env("MOBILIZON_SMTP_HOSTNAME", "localhost"),
|
||||
port: System.get_env("MOBILIZON_SMTP_PORT", "25"),
|
||||
username: System.get_env("MOBILIZON_SMTP_USERNAME", nil),
|
||||
password: System.get_env("MOBILIZON_SMTP_PASSWORD", nil),
|
||||
tls: :if_available,
|
||||
allowed_tls_versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"],
|
||||
ssl: System.get_env("MOBILIZON_SMTP_SSL", "false"),
|
||||
retries: 1,
|
||||
no_mx_lookups: false,
|
||||
auth: :if_available
|
|
@ -79,7 +79,7 @@ defmodule Mobilizon.Federation.ActivityPub.Federator do
|
|||
def enqueue(type, payload, priority \\ 1) do
|
||||
Logger.debug("enqueue something with type #{inspect(type)}")
|
||||
|
||||
if Mix.env() == :test do
|
||||
if Application.fetch_env!(:mobilizon, :env) == :test do
|
||||
handle(type, payload)
|
||||
else
|
||||
GenServer.cast(__MODULE__, {:enqueue, type, payload, priority})
|
||||
|
|
|
@ -6,12 +6,18 @@ defmodule Mix.Tasks.Mobilizon.Actors do
|
|||
use Mix.Task
|
||||
|
||||
alias Mix.Tasks
|
||||
import Mix.Tasks.Mobilizon.Common
|
||||
|
||||
@shortdoc "Manages Mobilizon actors"
|
||||
|
||||
@impl Mix.Task
|
||||
def run(_) do
|
||||
Mix.shell().info("\nAvailable tasks:")
|
||||
shell_info("\nAvailable tasks:")
|
||||
|
||||
if mix_shell?() do
|
||||
Tasks.Help.run(["--search", "mobilizon.actors."])
|
||||
else
|
||||
show_subtasks_for_module(__MODULE__)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,6 +7,7 @@ defmodule Mix.Tasks.Mobilizon.Actors.Refresh do
|
|||
alias Mobilizon.Federation.ActivityPub
|
||||
alias Mobilizon.Storage.Repo
|
||||
import Ecto.Query
|
||||
import Mix.Tasks.Mobilizon.Common
|
||||
require Logger
|
||||
|
||||
@shortdoc "Refresh an actor or all actors"
|
||||
|
@ -26,11 +27,11 @@ defmodule Mix.Tasks.Mobilizon.Actors.Refresh do
|
|||
|
||||
verbose = Keyword.get(options, :verbose, false)
|
||||
|
||||
Mix.Task.run("app.start")
|
||||
start_mobilizon()
|
||||
|
||||
total = count_actors()
|
||||
|
||||
Mix.shell().info("""
|
||||
shell_info("""
|
||||
#{total} actors to process
|
||||
""")
|
||||
|
||||
|
@ -62,22 +63,22 @@ defmodule Mix.Tasks.Mobilizon.Actors.Refresh do
|
|||
|
||||
@impl Mix.Task
|
||||
def run([preferred_username]) do
|
||||
Mix.Task.run("app.start")
|
||||
start_mobilizon()
|
||||
|
||||
case ActivityPub.make_actor_from_nickname(preferred_username) do
|
||||
{:ok, %Actor{}} ->
|
||||
Mix.shell().info("""
|
||||
shell_info("""
|
||||
Actor #{preferred_username} refreshed
|
||||
""")
|
||||
|
||||
{:actor, nil} ->
|
||||
Mix.raise("Error: No such actor")
|
||||
shell_error("Error: No such actor")
|
||||
end
|
||||
end
|
||||
|
||||
@impl Mix.Task
|
||||
def run(_) do
|
||||
Mix.raise("mobilizon.actors.refresh requires an username as argument or --all as an option")
|
||||
shell_error("mobilizon.actors.refresh requires an username as argument or --all as an option")
|
||||
end
|
||||
|
||||
@spec make_actor(String.t(), boolean()) :: any()
|
||||
|
|
|
@ -5,16 +5,17 @@ defmodule Mix.Tasks.Mobilizon.Actors.Show do
|
|||
use Mix.Task
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.Actor
|
||||
import Mix.Tasks.Mobilizon.Common
|
||||
|
||||
@shortdoc "Show a Mobilizon user details"
|
||||
|
||||
@impl Mix.Task
|
||||
def run([preferred_username]) do
|
||||
Mix.Task.run("app.start")
|
||||
start_mobilizon()
|
||||
|
||||
case {:actor, Actors.get_actor_by_name_with_preload(preferred_username)} do
|
||||
{:actor, %Actor{} = actor} ->
|
||||
Mix.shell().info("""
|
||||
shell_info("""
|
||||
Informations for the actor #{actor.preferred_username}:
|
||||
- Type: #{actor.type}
|
||||
- Domain: #{if is_nil(actor.domain), do: "Local", else: actor.domain}
|
||||
|
@ -24,11 +25,11 @@ defmodule Mix.Tasks.Mobilizon.Actors.Show do
|
|||
""")
|
||||
|
||||
{:actor, nil} ->
|
||||
Mix.raise("Error: No such actor")
|
||||
shell_error("Error: No such actor")
|
||||
end
|
||||
end
|
||||
|
||||
def run(_) do
|
||||
Mix.raise("mobilizon.actors.show requires an username as argument")
|
||||
shell_error("mobilizon.actors.show requires an username as argument")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,32 +8,107 @@ defmodule Mix.Tasks.Mobilizon.Common do
|
|||
Common functions to be reused in mix tasks
|
||||
"""
|
||||
|
||||
def get_option(options, opt, prompt, defval \\ nil, defname \\ nil) do
|
||||
display = if defname || defval, do: "#{prompt} [#{defname || defval}]", else: "#{prompt}"
|
||||
|
||||
Keyword.get(options, opt) ||
|
||||
case Mix.shell().prompt(display) do
|
||||
"\n" ->
|
||||
case defval do
|
||||
nil ->
|
||||
get_option(options, opt, prompt, defval)
|
||||
|
||||
defval ->
|
||||
defval
|
||||
end
|
||||
|
||||
opt ->
|
||||
String.trim(opt)
|
||||
end
|
||||
end
|
||||
|
||||
def start_mobilizon do
|
||||
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
|
||||
|
||||
{:ok, _} = Application.ensure_all_started(:mobilizon)
|
||||
end
|
||||
|
||||
def get_option(options, opt, prompt, defval \\ nil, defname \\ nil) do
|
||||
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
|
||||
if mix_shell?(),
|
||||
do: Mix.shell().yes?("Continue?"),
|
||||
else: shell_prompt(message, "Continue?") 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
|
||||
|
||||
@type task_module :: atom
|
||||
|
||||
@doc """
|
||||
Gets the shortdoc for the given task `module`.
|
||||
Returns the shortdoc or `nil`.
|
||||
"""
|
||||
@spec shortdoc(task_module) :: String.t() | nil
|
||||
def shortdoc(module) when is_atom(module) do
|
||||
case List.keyfind(module.__info__(:attributes), :shortdoc, 0) do
|
||||
{:shortdoc, [shortdoc]} -> shortdoc
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def show_subtasks_for_module(module_name) do
|
||||
tasks = list_subtasks_for_module(module_name)
|
||||
|
||||
max = Enum.reduce(tasks, 0, fn {name, _doc}, acc -> max(byte_size(name), acc) end)
|
||||
|
||||
Enum.each(tasks, fn {name, doc} ->
|
||||
shell_info("#{String.pad_trailing(name, max + 2)} # #{doc}")
|
||||
end)
|
||||
end
|
||||
|
||||
@spec list_subtasks_for_module(atom()) :: list({String.t(), String.t()})
|
||||
def list_subtasks_for_module(module_name) do
|
||||
Application.load(:mobilizon)
|
||||
{:ok, modules} = :application.get_key(:mobilizon, :modules)
|
||||
module_name = to_string(module_name)
|
||||
|
||||
modules
|
||||
|> Enum.filter(fn module ->
|
||||
String.starts_with?(to_string(module), to_string(module_name)) &&
|
||||
to_string(module) != to_string(module_name)
|
||||
end)
|
||||
|> Enum.map(&format_module/1)
|
||||
end
|
||||
|
||||
defp format_module(module) do
|
||||
{format_name(to_string(module)), shortdoc(module)}
|
||||
end
|
||||
|
||||
defp format_name("Elixir.Mix.Tasks.Mobilizon." <> task_name) do
|
||||
String.downcase(task_name)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,12 +8,13 @@ defmodule Mix.Tasks.Mobilizon.CreateBot do
|
|||
alias Mobilizon.{Actors, Users}
|
||||
alias Mobilizon.Actors.Bot
|
||||
alias Mobilizon.Users.User
|
||||
import Mix.Tasks.Mobilizon.Common
|
||||
|
||||
require Logger
|
||||
|
||||
@shortdoc "Create bot"
|
||||
def run([email, name, summary, type, url]) do
|
||||
Mix.Task.run("app.start")
|
||||
start_mobilizon()
|
||||
|
||||
with {:ok, %User{} = user} <- Users.get_user_by_email(email, true),
|
||||
actor <- Actors.register_bot(%{name: name, summary: summary}),
|
||||
|
|
54
lib/mix/tasks/mobilizon/ecto.ex
Normal file
54
lib/mix/tasks/mobilizon/ecto.ex
Normal file
|
@ -0,0 +1,54 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-onl
|
||||
|
||||
defmodule Mix.Tasks.Mobilizon.Ecto do
|
||||
@moduledoc """
|
||||
Provides tools for Ecto-related tasks (such as migrations)
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Ensures the given repository's migrations path exists on the file system.
|
||||
"""
|
||||
@spec ensure_migrations_path(Ecto.Repo.t(), Keyword.t()) :: String.t()
|
||||
def ensure_migrations_path(repo, opts) do
|
||||
path = opts[:migrations_path] || Path.join(source_repo_priv(repo), "migrations")
|
||||
|
||||
path =
|
||||
case Path.type(path) do
|
||||
:relative ->
|
||||
Path.join(Application.app_dir(:mobilizon), path)
|
||||
|
||||
:absolute ->
|
||||
path
|
||||
end
|
||||
|
||||
if not File.dir?(path) do
|
||||
raise_missing_migrations(Path.relative_to_cwd(path), repo)
|
||||
end
|
||||
|
||||
path
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the private repository path relative to the source.
|
||||
"""
|
||||
def source_repo_priv(repo) do
|
||||
config = repo.config()
|
||||
priv = config[:priv] || "priv/#{repo |> Module.split() |> List.last() |> Macro.underscore()}"
|
||||
Path.join(Application.app_dir(:mobilizon), priv)
|
||||
end
|
||||
|
||||
defp raise_missing_migrations(path, repo) do
|
||||
raise("""
|
||||
Could not find migrations directory #{inspect(path)}
|
||||
for repo #{inspect(repo)}.
|
||||
This may be because you are in a new project and the
|
||||
migration directory has not been created yet. Creating an
|
||||
empty directory at the path above will fix this error.
|
||||
If you expected existing migrations to be found, please
|
||||
make sure your repository has been properly configured
|
||||
and the configured path exists.
|
||||
""")
|
||||
end
|
||||
end
|
71
lib/mix/tasks/mobilizon/ecto/migrate.ex
Normal file
71
lib/mix/tasks/mobilizon/ecto/migrate.ex
Normal file
|
@ -0,0 +1,71 @@
|
|||
# Portions of this file are derived from Pleroma:
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mix.Tasks.Mobilizon.Ecto.Migrate do
|
||||
use Mix.Task
|
||||
import Mix.Tasks.Mobilizon.Common
|
||||
alias Mix.Tasks.Mobilizon.Ecto, as: EctoTask
|
||||
require Logger
|
||||
|
||||
@shortdoc "Wrapper on `ecto.migrate` task."
|
||||
|
||||
@aliases [
|
||||
n: :step,
|
||||
v: :to
|
||||
]
|
||||
|
||||
@switches [
|
||||
all: :boolean,
|
||||
step: :integer,
|
||||
to: :integer,
|
||||
quiet: :boolean,
|
||||
log_sql: :boolean,
|
||||
strict_version_order: :boolean,
|
||||
migrations_path: :string
|
||||
]
|
||||
|
||||
@repo Mobilizon.Storage.Repo
|
||||
|
||||
@moduledoc """
|
||||
Changes `Logger` level to `:info` before start migration.
|
||||
Changes level back when migration ends.
|
||||
|
||||
## Start migration
|
||||
|
||||
mix mobilizon.ecto.migrate [OPTIONS]
|
||||
|
||||
Options:
|
||||
- see https://hexdocs.pm/ecto/2.0.0/Mix.Tasks.Ecto.Migrate.html
|
||||
"""
|
||||
|
||||
@impl true
|
||||
def run(args \\ []) do
|
||||
start_mobilizon()
|
||||
{opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases)
|
||||
|
||||
if Application.get_env(:mobilizon, @repo)[:ssl] do
|
||||
Application.ensure_all_started(:ssl)
|
||||
end
|
||||
|
||||
opts =
|
||||
if opts[:to] || opts[:step] || opts[:all],
|
||||
do: opts,
|
||||
else: Keyword.put(opts, :all, true)
|
||||
|
||||
opts =
|
||||
if opts[:quiet],
|
||||
do: Keyword.merge(opts, log: false, log_sql: false),
|
||||
else: opts
|
||||
|
||||
path = EctoTask.ensure_migrations_path(@repo, opts)
|
||||
|
||||
level = Logger.level()
|
||||
Logger.configure(level: :info)
|
||||
|
||||
{:ok, _, _} = Ecto.Migrator.with_repo(@repo, &Ecto.Migrator.run(&1, path, :up, opts))
|
||||
|
||||
Logger.configure(level: level)
|
||||
end
|
||||
end
|
74
lib/mix/tasks/mobilizon/ecto/rollback.ex
Normal file
74
lib/mix/tasks/mobilizon/ecto/rollback.ex
Normal file
|
@ -0,0 +1,74 @@
|
|||
# Portions of this file are derived from Pleroma:
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mix.Tasks.Mobilizon.Ecto.Rollback do
|
||||
use Mix.Task
|
||||
import Mix.Tasks.Mobilizon.Common
|
||||
alias Mix.Tasks.Mobilizon.Ecto, as: EctoTask
|
||||
require Logger
|
||||
@shortdoc "Wrapper on `ecto.rollback` task"
|
||||
|
||||
@aliases [
|
||||
n: :step,
|
||||
v: :to
|
||||
]
|
||||
|
||||
@switches [
|
||||
all: :boolean,
|
||||
step: :integer,
|
||||
to: :integer,
|
||||
start: :boolean,
|
||||
quiet: :boolean,
|
||||
log_sql: :boolean,
|
||||
migrations_path: :string
|
||||
]
|
||||
|
||||
@repo Mobilizon.Storage.Repo
|
||||
|
||||
@moduledoc """
|
||||
Changes `Logger` level to `:info` before start rollback.
|
||||
Changes level back when rollback ends.
|
||||
|
||||
## Start rollback
|
||||
|
||||
mix mobilizon.ecto.rollback
|
||||
|
||||
Options:
|
||||
- see https://hexdocs.pm/ecto/2.0.0/Mix.Tasks.Ecto.Rollback.html
|
||||
"""
|
||||
|
||||
@impl true
|
||||
def run(args \\ []) do
|
||||
start_mobilizon()
|
||||
{opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases)
|
||||
|
||||
if Application.get_env(:mobilizon, @repo)[:ssl] do
|
||||
Application.ensure_all_started(:ssl)
|
||||
end
|
||||
|
||||
opts =
|
||||
if opts[:to] || opts[:step] || opts[:all],
|
||||
do: opts,
|
||||
else: Keyword.put(opts, :step, 1)
|
||||
|
||||
opts =
|
||||
if opts[:quiet],
|
||||
do: Keyword.merge(opts, log: false, log_sql: false),
|
||||
else: opts
|
||||
|
||||
path = EctoTask.ensure_migrations_path(@repo, opts)
|
||||
|
||||
level = Logger.level()
|
||||
Logger.configure(level: :info)
|
||||
|
||||
if Mobilizon.Config.get(:env) == :test do
|
||||
Logger.info("Rollback succesfully")
|
||||
else
|
||||
{:ok, _, _} = Ecto.Migrator.with_repo(@repo, &Ecto.Migrator.run(&1, path, :down, opts))
|
||||
end
|
||||
|
||||
Logger.configure(level: level)
|
||||
end
|
||||
end
|
|
@ -6,12 +6,13 @@ defmodule Mix.Tasks.Mobilizon.Groups do
|
|||
use Mix.Task
|
||||
|
||||
alias Mix.Tasks
|
||||
import Mix.Tasks.Mobilizon.Common
|
||||
|
||||
@shortdoc "Manages Mobilizon groups"
|
||||
|
||||
@impl Mix.Task
|
||||
def run(_) do
|
||||
Mix.shell().info("\nAvailable tasks:")
|
||||
shell_info("\nAvailable tasks:")
|
||||
Tasks.Help.run(["--search", "mobilizon.groups."])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,12 +6,13 @@ defmodule Mix.Tasks.Mobilizon.Groups.Refresh do
|
|||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Federation.ActivityPub.Refresher
|
||||
import Mix.Tasks.Mobilizon.Common
|
||||
|
||||
@shortdoc "Refresh a group private informations from an account member"
|
||||
|
||||
@impl Mix.Task
|
||||
def run([group_url, on_behalf_of]) do
|
||||
Mix.Task.run("app.start")
|
||||
start_mobilizon()
|
||||
|
||||
with %Actor{} = actor <- Actors.get_local_actor_by_name(on_behalf_of) do
|
||||
res = Refresher.fetch_group(group_url, actor)
|
||||
|
@ -20,7 +21,7 @@ defmodule Mix.Tasks.Mobilizon.Groups.Refresh do
|
|||
end
|
||||
|
||||
def run(_) do
|
||||
Mix.raise(
|
||||
shell_error(
|
||||
"mobilizon.groups.refresh requires a group URL and an actor username which is member of the group as arguments"
|
||||
)
|
||||
end
|
||||
|
|
|
@ -29,7 +29,7 @@ defmodule Mix.Tasks.Mobilizon.Instance do
|
|||
|
||||
use Mix.Task
|
||||
|
||||
alias Mix.Tasks.Mobilizon.Common
|
||||
import Mix.Tasks.Mobilizon.Common
|
||||
|
||||
@preferred_cli_env "prod"
|
||||
|
||||
|
@ -70,7 +70,7 @@ defmodule Mix.Tasks.Mobilizon.Instance do
|
|||
if proceed? do
|
||||
[domain, port | _] =
|
||||
String.split(
|
||||
Common.get_option(
|
||||
get_option(
|
||||
options,
|
||||
:domain,
|
||||
"What domain will your instance use? (e.g mobilizon.org)"
|
||||
|
@ -79,25 +79,24 @@ defmodule Mix.Tasks.Mobilizon.Instance do
|
|||
) ++ [443]
|
||||
|
||||
name =
|
||||
Common.get_option(
|
||||
get_option(
|
||||
options,
|
||||
:instance_name,
|
||||
"What is the name of your instance? (e.g. Mobilizon)"
|
||||
)
|
||||
|
||||
email =
|
||||
Common.get_option(
|
||||
get_option(
|
||||
options,
|
||||
:admin_email,
|
||||
"What's the address email will be send with?",
|
||||
"noreply@#{domain}"
|
||||
)
|
||||
|
||||
dbhost =
|
||||
Common.get_option(options, :dbhost, "What is the hostname of your database?", "localhost")
|
||||
dbhost = get_option(options, :dbhost, "What is the hostname of your database?", "localhost")
|
||||
|
||||
dbname =
|
||||
Common.get_option(
|
||||
get_option(
|
||||
options,
|
||||
:dbname,
|
||||
"What is the name of your database?",
|
||||
|
@ -105,7 +104,7 @@ defmodule Mix.Tasks.Mobilizon.Instance do
|
|||
)
|
||||
|
||||
dbuser =
|
||||
Common.get_option(
|
||||
get_option(
|
||||
options,
|
||||
:dbuser,
|
||||
"What is the user used to connect to your database?",
|
||||
|
@ -113,7 +112,7 @@ defmodule Mix.Tasks.Mobilizon.Instance do
|
|||
)
|
||||
|
||||
dbpass =
|
||||
Common.get_option(
|
||||
get_option(
|
||||
options,
|
||||
:dbpass,
|
||||
"What is the password used to connect to your database?",
|
||||
|
@ -122,7 +121,7 @@ defmodule Mix.Tasks.Mobilizon.Instance do
|
|||
)
|
||||
|
||||
listen_port =
|
||||
Common.get_option(
|
||||
get_option(
|
||||
options,
|
||||
:listen_port,
|
||||
"What port will the app listen to (leave it if you are using the default setup with nginx)?",
|
||||
|
@ -160,24 +159,24 @@ defmodule Mix.Tasks.Mobilizon.Instance do
|
|||
database_password: dbpass
|
||||
)
|
||||
|
||||
Mix.shell().info("Writing config to #{config_path}.")
|
||||
shell_info("Writing config to #{config_path}.")
|
||||
|
||||
File.write(config_path, result_config)
|
||||
Mix.shell().info("Writing #{psql_path}.")
|
||||
shell_info("Writing #{psql_path}.")
|
||||
File.write(psql_path, result_psql)
|
||||
|
||||
Mix.shell().info(
|
||||
shell_info(
|
||||
"\n" <>
|
||||
"""
|
||||
To get started:
|
||||
1. Check the contents of the generated files.
|
||||
2. Run `sudo -u postgres psql -f #{Common.escape_sh_path(psql_path)} && rm #{
|
||||
Common.escape_sh_path(psql_path)
|
||||
2. Run `sudo -u postgres psql -f #{escape_sh_path(psql_path)} && rm #{
|
||||
escape_sh_path(psql_path)
|
||||
}`.
|
||||
"""
|
||||
)
|
||||
else
|
||||
Mix.shell().error(
|
||||
shell_error(
|
||||
"The task would have overwritten the following files:\n" <>
|
||||
(will_overwrite |> Enum.map(&"- #{&1}\n") |> Enum.join("")) <>
|
||||
"Rerun with `-f/--force` to overwrite them."
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
defmodule Mix.Tasks.Mobilizon.MoveParticipantStats do
|
||||
@moduledoc """
|
||||
Temporary task to move participant stats in the events table
|
||||
|
||||
This task will be removed in version 1.0.0-beta.3
|
||||
"""
|
||||
|
||||
use Mix.Task
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Events.ParticipantRole
|
||||
alias Mobilizon.Storage.Repo
|
||||
|
||||
require Logger
|
||||
|
||||
@shortdoc "Move participant stats to events table"
|
||||
def run([]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
events =
|
||||
Event
|
||||
|> preload([e], :tags)
|
||||
|> Repo.all()
|
||||
|
||||
nb_events = length(events)
|
||||
|
||||
IO.puts(
|
||||
"\nStarting inserting participants stats into #{nb_events} events, this can take a while…\n"
|
||||
)
|
||||
|
||||
insert_participants_stats_into_events(events, nb_events)
|
||||
end
|
||||
|
||||
defp insert_participants_stats_into_events([%Event{url: url} = event | events], nb_events) do
|
||||
with roles <- ParticipantRole.__enum_map__(),
|
||||
counts <-
|
||||
Enum.reduce(roles, %{}, fn role, acc ->
|
||||
Map.put(acc, role, count_participants(event, role))
|
||||
end),
|
||||
{:ok, _} <-
|
||||
Events.update_event(event, %{
|
||||
participant_stats: counts
|
||||
}) do
|
||||
Logger.debug("Added participants stats to event #{url}")
|
||||
else
|
||||
{:error, res} ->
|
||||
Logger.error("Error while adding participants stats to event #{url} : #{inspect(res)}")
|
||||
end
|
||||
|
||||
ProgressBar.render(nb_events - length(events), nb_events)
|
||||
|
||||
insert_participants_stats_into_events(events, nb_events)
|
||||
end
|
||||
|
||||
defp insert_participants_stats_into_events([], nb_events) do
|
||||
IO.puts("\nFinished inserting participant stats for #{nb_events} events!\n")
|
||||
end
|
||||
|
||||
defp count_participants(%Event{id: event_id}, role) when is_atom(role) do
|
||||
event_id
|
||||
|> Events.count_participants_query()
|
||||
|> Events.filter_role(role)
|
||||
|> Repo.aggregate(:count, :id)
|
||||
end
|
||||
end
|
|
@ -22,60 +22,19 @@ defmodule Mix.Tasks.Mobilizon.Relay do
|
|||
|
||||
use Mix.Task
|
||||
|
||||
alias Mix.Tasks.Mobilizon.Common
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub.Relay
|
||||
alias Mix.Tasks
|
||||
import Mix.Tasks.Mobilizon.Common
|
||||
|
||||
@shortdoc "Manages remote relays"
|
||||
def run(["follow", target]) do
|
||||
Common.start_mobilizon()
|
||||
|
||||
case Relay.follow(target) do
|
||||
{:ok, _activity, _follow} ->
|
||||
# put this task to sleep to allow the genserver to push out the messages
|
||||
:timer.sleep(500)
|
||||
@impl Mix.Task
|
||||
def run(_) do
|
||||
shell_info("\nAvailable tasks:")
|
||||
|
||||
{:error, e} ->
|
||||
IO.puts(:stderr, "Error while following #{target}: #{inspect(e)}")
|
||||
end
|
||||
end
|
||||
|
||||
def run(["unfollow", target]) do
|
||||
Common.start_mobilizon()
|
||||
|
||||
case Relay.unfollow(target) do
|
||||
{:ok, _activity, _follow} ->
|
||||
# put this task to sleep to allow the genserver to push out the messages
|
||||
:timer.sleep(500)
|
||||
|
||||
{:error, e} ->
|
||||
IO.puts(:stderr, "Error while unfollowing #{target}: #{inspect(e)}")
|
||||
end
|
||||
end
|
||||
|
||||
def run(["accept", target]) do
|
||||
Common.start_mobilizon()
|
||||
|
||||
case Relay.accept(target) do
|
||||
{:ok, _activity} ->
|
||||
# put this task to sleep to allow the genserver to push out the messages
|
||||
:timer.sleep(500)
|
||||
|
||||
{:error, e} ->
|
||||
IO.puts(:stderr, "Error while accept #{target} follow: #{inspect(e)}")
|
||||
end
|
||||
end
|
||||
|
||||
def run(["refresh", target]) do
|
||||
Common.start_mobilizon()
|
||||
IO.puts("Refreshing #{target}, this can take a while.")
|
||||
|
||||
case Relay.refresh(target) do
|
||||
:ok ->
|
||||
IO.puts("Refreshed #{target}")
|
||||
|
||||
err ->
|
||||
IO.puts(:stderr, "Error while refreshing #{target}: #{inspect(err)}")
|
||||
if mix_shell?() do
|
||||
Tasks.Help.run(["--search", "mobilizon.relay."])
|
||||
else
|
||||
show_subtasks_for_module(__MODULE__)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
28
lib/mix/tasks/mobilizon/relay/accept.ex
Normal file
28
lib/mix/tasks/mobilizon/relay/accept.ex
Normal file
|
@ -0,0 +1,28 @@
|
|||
defmodule Mix.Tasks.Mobilizon.Relay.Accept do
|
||||
@moduledoc """
|
||||
Task to accept an instance follow request
|
||||
"""
|
||||
use Mix.Task
|
||||
alias Mobilizon.Federation.ActivityPub.Relay
|
||||
import Mix.Tasks.Mobilizon.Common
|
||||
|
||||
@shortdoc "Accept an instance follow request"
|
||||
|
||||
@impl Mix.Task
|
||||
def run([target]) do
|
||||
start_mobilizon()
|
||||
|
||||
case Relay.accept(target) do
|
||||
{:ok, _activity} ->
|
||||
# put this task to sleep to allow the genserver to push out the messages
|
||||
:timer.sleep(500)
|
||||
|
||||
{:error, e} ->
|
||||
IO.puts(:stderr, "Error while accept #{target} follow: #{inspect(e)}")
|
||||
end
|
||||
end
|
||||
|
||||
def run(_) do
|
||||
shell_error("mobilizon.relay.accept requires an instance hostname as arguments")
|
||||
end
|
||||
end
|
28
lib/mix/tasks/mobilizon/relay/follow.ex
Normal file
28
lib/mix/tasks/mobilizon/relay/follow.ex
Normal file
|
@ -0,0 +1,28 @@
|
|||
defmodule Mix.Tasks.Mobilizon.Relay.Follow do
|
||||
@moduledoc """
|
||||
Task to follow an instance
|
||||
"""
|
||||
use Mix.Task
|
||||
alias Mobilizon.Federation.ActivityPub.Relay
|
||||
import Mix.Tasks.Mobilizon.Common
|
||||
|
||||
@shortdoc "Follow an instance"
|
||||
|
||||
@impl Mix.Task
|
||||
def run([target]) do
|
||||
start_mobilizon()
|
||||
|
||||
case Relay.follow(target) do
|
||||
{:ok, _activity, _follow} ->
|
||||
# put this task to sleep to allow the genserver to push out the messages
|
||||
:timer.sleep(500)
|
||||
|
||||
{:error, e} ->
|
||||
IO.puts(:stderr, "Error while following #{target}: #{inspect(e)}")
|
||||
end
|
||||
end
|
||||
|
||||
def run(_) do
|
||||
shell_error("mobilizon.relay.follow requires an instance hostname as arguments")
|
||||
end
|
||||
end
|
28
lib/mix/tasks/mobilizon/relay/refresh.ex
Normal file
28
lib/mix/tasks/mobilizon/relay/refresh.ex
Normal file
|
@ -0,0 +1,28 @@
|
|||
defmodule Mix.Tasks.Mobilizon.Relay.Refresh do
|
||||
@moduledoc """
|
||||
Task to refresh an instance details
|
||||
"""
|
||||
use Mix.Task
|
||||
alias Mobilizon.Federation.ActivityPub.Relay
|
||||
import Mix.Tasks.Mobilizon.Common
|
||||
|
||||
@shortdoc "Refresh an instance informations and crawl their outbox"
|
||||
|
||||
@impl Mix.Task
|
||||
def run([target]) do
|
||||
start_mobilizon()
|
||||
IO.puts("Refreshing #{target}, this can take a while.")
|
||||
|
||||
case Relay.refresh(target) do
|
||||
:ok ->
|
||||
IO.puts("Refreshed #{target}")
|
||||
|
||||
err ->
|
||||
IO.puts(:stderr, "Error while refreshing #{target}: #{inspect(err)}")
|
||||
end
|
||||
end
|
||||
|
||||
def run(_) do
|
||||
shell_error("mobilizon.relay.refresh requires an instance hostname as arguments")
|
||||
end
|
||||
end
|
28
lib/mix/tasks/mobilizon/relay/unfollow.ex
Normal file
28
lib/mix/tasks/mobilizon/relay/unfollow.ex
Normal file
|
@ -0,0 +1,28 @@
|
|||
defmodule Mix.Tasks.Mobilizon.Relay.Unfollow do
|
||||
@moduledoc """
|
||||
Task to unfollow an instance
|
||||
"""
|
||||
use Mix.Task
|
||||
alias Mobilizon.Federation.ActivityPub.Relay
|
||||
import Mix.Tasks.Mobilizon.Common
|
||||
|
||||
@shortdoc "Unfollow an instance"
|
||||
|
||||
@impl Mix.Task
|
||||
def run([target]) do
|
||||
start_mobilizon()
|
||||
|
||||
case Relay.unfollow(target) do
|
||||
{:ok, _activity, _follow} ->
|
||||
# put this task to sleep to allow the genserver to push out the messages
|
||||
:timer.sleep(500)
|
||||
|
||||
{:error, e} ->
|
||||
IO.puts(:stderr, "Error while unfollowing #{target}: #{inspect(e)}")
|
||||
end
|
||||
end
|
||||
|
||||
def run(_) do
|
||||
shell_error("mobilizon.relay.unfollow requires an instance hostname as arguments")
|
||||
end
|
||||
end
|
|
@ -1,50 +0,0 @@
|
|||
defmodule Mix.Tasks.Mobilizon.SetupSearch do
|
||||
@moduledoc """
|
||||
Temporary task to insert search data from existing events
|
||||
|
||||
This task will be removed in version 1.0.0-beta.3
|
||||
"""
|
||||
|
||||
use Mix.Task
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Service.Workers
|
||||
alias Mobilizon.Storage.Repo
|
||||
|
||||
require Logger
|
||||
|
||||
@shortdoc "Insert search data"
|
||||
def run([]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
events =
|
||||
Event
|
||||
|> preload([e], :tags)
|
||||
|> Repo.all()
|
||||
|
||||
nb_events = length(events)
|
||||
|
||||
IO.puts("\nStarting setting up search for #{nb_events} events, this can take a while…\n")
|
||||
insert_search_event(events, nb_events)
|
||||
end
|
||||
|
||||
defp insert_search_event([%Event{url: url} = event | events], nb_events) do
|
||||
case Workers.BuildSearch.insert_search_event(event) do
|
||||
{:ok, _} ->
|
||||
Logger.debug("Added event #{url} to the search")
|
||||
|
||||
{:error, res} ->
|
||||
Logger.error("Error while adding event #{url} to the search: #{inspect(res)}")
|
||||
end
|
||||
|
||||
ProgressBar.render(nb_events - length(events), nb_events)
|
||||
|
||||
insert_search_event(events, nb_events)
|
||||
end
|
||||
|
||||
defp insert_search_event([], nb_events) do
|
||||
IO.puts("\nFinished setting up search for #{nb_events} events!\n")
|
||||
end
|
||||
end
|
|
@ -4,7 +4,7 @@ defmodule Mix.Tasks.Mobilizon.SiteMap do
|
|||
"""
|
||||
use Mix.Task
|
||||
|
||||
alias Mix.Tasks.Mobilizon.Common
|
||||
import Mix.Tasks.Mobilizon.Common
|
||||
alias Mobilizon.Service.SiteMap
|
||||
alias Mobilizon.Web.Endpoint
|
||||
|
||||
|
@ -12,10 +12,10 @@ defmodule Mix.Tasks.Mobilizon.SiteMap do
|
|||
|
||||
@shortdoc "Generates a new Sitemap"
|
||||
def run(["generate"]) do
|
||||
Common.start_mobilizon()
|
||||
start_mobilizon()
|
||||
|
||||
with {:ok, :ok} <- SiteMap.generate_sitemap() do
|
||||
Mix.shell().info("Sitemap saved to #{Endpoint.url()}/sitemap.xml")
|
||||
shell_info("Sitemap saved to #{Endpoint.url()}/sitemap.xml")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
defmodule Mix.Tasks.Mobilizon.Toot do
|
||||
@moduledoc """
|
||||
Creates a bot from a source.
|
||||
"""
|
||||
|
||||
use Mix.Task
|
||||
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.Actor
|
||||
|
||||
alias Mobilizon.GraphQL.API.Comments
|
||||
|
||||
require Logger
|
||||
|
||||
@shortdoc "Toot to an user"
|
||||
def run([from, text]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
with {:local_actor, %Actor{} = actor} <- {:local_actor, Actors.get_local_actor_by_name(from)},
|
||||
{:ok, _, _} <- Comments.create_comment(%{actor: actor, text: text}) do
|
||||
Mix.shell().info("Tooted")
|
||||
else
|
||||
{:local_actor, _, _} ->
|
||||
Mix.shell().error("Failed to toot.\nActor #{from} doesn't exist")
|
||||
|
||||
_ ->
|
||||
Mix.shell().error("Failed to toot.")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,12 +6,18 @@ defmodule Mix.Tasks.Mobilizon.Users do
|
|||
use Mix.Task
|
||||
|
||||
alias Mix.Tasks
|
||||
import Mix.Tasks.Mobilizon.Common
|
||||
|
||||
@shortdoc "Manages Mobilizon users"
|
||||
|
||||
@impl Mix.Task
|
||||
def run(_) do
|
||||
Mix.shell().info("\nAvailable tasks:")
|
||||
shell_info("\nAvailable tasks:")
|
||||
|
||||
if mix_shell?() do
|
||||
Tasks.Help.run(["--search", "mobilizon.users."])
|
||||
else
|
||||
show_subtasks_for_module(__MODULE__)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,6 +5,7 @@ defmodule Mix.Tasks.Mobilizon.Users.Delete do
|
|||
use Mix.Task
|
||||
alias Mobilizon.Users
|
||||
alias Mobilizon.Users.User
|
||||
import Mix.Tasks.Mobilizon.Common
|
||||
|
||||
@shortdoc "Deletes a Mobilizon user"
|
||||
|
||||
|
@ -26,25 +27,25 @@ defmodule Mix.Tasks.Mobilizon.Users.Delete do
|
|||
assume_yes? = Keyword.get(options, :assume_yes, false)
|
||||
keep_email? = Keyword.get(options, :keep_email, false)
|
||||
|
||||
Mix.Task.run("app.start")
|
||||
start_mobilizon()
|
||||
|
||||
with {:ok, %User{} = user} <- Users.get_user_by_email(email),
|
||||
true <- assume_yes? or Mix.shell().yes?("Continue with deleting user #{user.email}?"),
|
||||
true <- assume_yes? or shell_yes?("Continue with deleting user #{user.email}?"),
|
||||
{:ok, %User{} = user} <-
|
||||
Users.delete_user(user, reserve_email: keep_email?) do
|
||||
Mix.shell().info("""
|
||||
shell_info("""
|
||||
The user #{user.email} has been deleted
|
||||
""")
|
||||
else
|
||||
{:error, :user_not_found} ->
|
||||
Mix.raise("Error: No such user")
|
||||
shell_error("Error: No such user")
|
||||
|
||||
_ ->
|
||||
Mix.raise("User has not been deleted.")
|
||||
shell_error("User has not been deleted.")
|
||||
end
|
||||
end
|
||||
|
||||
def run(_) do
|
||||
Mix.raise("mobilizon.users.delete requires an email as argument")
|
||||
shell_error("mobilizon.users.delete requires an email as argument")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,7 @@ defmodule Mix.Tasks.Mobilizon.Users.Modify do
|
|||
Task to modify an existing Mobilizon user
|
||||
"""
|
||||
use Mix.Task
|
||||
import Mix.Tasks.Mobilizon.Common
|
||||
alias Mobilizon.Users
|
||||
alias Mobilizon.Users.User
|
||||
|
||||
|
@ -31,10 +32,10 @@ defmodule Mix.Tasks.Mobilizon.Users.Modify do
|
|||
new_email = Keyword.get(options, :email)
|
||||
|
||||
if disable? && enable? do
|
||||
Mix.raise("Can't use both --enabled and --disable options at the same time.")
|
||||
shell_error("Can't use both --enabled and --disable options at the same time.")
|
||||
end
|
||||
|
||||
Mix.Task.run("app.start")
|
||||
start_mobilizon()
|
||||
|
||||
with {:ok, %User{} = user} <- Users.get_user_by_email(email),
|
||||
attrs <- %{},
|
||||
|
@ -53,7 +54,7 @@ defmodule Mix.Tasks.Mobilizon.Users.Modify do
|
|||
),
|
||||
{:makes_changes, true} <- {:makes_changes, attrs != %{}},
|
||||
{:ok, %User{} = user} <- Users.update_user(user, attrs) do
|
||||
Mix.shell().info("""
|
||||
shell_info("""
|
||||
An user has been modified with the following information:
|
||||
- email: #{user.email}
|
||||
- Role: #{user.role}
|
||||
|
@ -61,23 +62,23 @@ defmodule Mix.Tasks.Mobilizon.Users.Modify do
|
|||
""")
|
||||
else
|
||||
{:makes_changes, false} ->
|
||||
Mix.shell().info("No change has been made")
|
||||
shell_info("No change has been made")
|
||||
|
||||
{:error, :user_not_found} ->
|
||||
Mix.raise("Error: No such user")
|
||||
shell_error("Error: No such user")
|
||||
|
||||
{:error, %Ecto.Changeset{errors: errors}} ->
|
||||
Mix.shell().error(inspect(errors))
|
||||
Mix.raise("User has not been modified because of the above reason.")
|
||||
shell_error(inspect(errors))
|
||||
shell_error("User has not been modified because of the above reason.")
|
||||
|
||||
err ->
|
||||
Mix.shell().error(inspect(err))
|
||||
Mix.raise("User has not been modified because of an unknown reason.")
|
||||
shell_error(inspect(err))
|
||||
shell_error("User has not been modified because of an unknown reason.")
|
||||
end
|
||||
end
|
||||
|
||||
def run(_) do
|
||||
Mix.raise("mobilizon.users.new requires an email as argument")
|
||||
shell_error("mobilizon.users.new requires an email as argument")
|
||||
end
|
||||
|
||||
@spec process_new_value(map(), atom(), any(), any()) :: map()
|
||||
|
|
|
@ -3,6 +3,7 @@ defmodule Mix.Tasks.Mobilizon.Users.New do
|
|||
Task to create a new user
|
||||
"""
|
||||
use Mix.Task
|
||||
import Mix.Tasks.Mobilizon.Common
|
||||
alias Mobilizon.Users
|
||||
alias Mobilizon.Users.User
|
||||
|
||||
|
@ -40,7 +41,7 @@ defmodule Mix.Tasks.Mobilizon.Users.New do
|
|||
:crypto.strong_rand_bytes(16) |> Base.encode64() |> binary_part(0, 16)
|
||||
)
|
||||
|
||||
Mix.Task.run("app.start")
|
||||
start_mobilizon()
|
||||
|
||||
case Users.register(%{
|
||||
email: email,
|
||||
|
@ -51,7 +52,7 @@ defmodule Mix.Tasks.Mobilizon.Users.New do
|
|||
confirmation_token: nil
|
||||
}) do
|
||||
{:ok, %User{} = user} ->
|
||||
Mix.shell().info("""
|
||||
shell_info("""
|
||||
An user has been created with the following information:
|
||||
- email: #{user.email}
|
||||
- password: #{password}
|
||||
|
@ -60,16 +61,16 @@ defmodule Mix.Tasks.Mobilizon.Users.New do
|
|||
""")
|
||||
|
||||
{:error, %Ecto.Changeset{errors: errors}} ->
|
||||
Mix.shell().error(inspect(errors))
|
||||
Mix.raise("User has not been created because of the above reason.")
|
||||
shell_error(inspect(errors))
|
||||
shell_error("User has not been created because of the above reason.")
|
||||
|
||||
err ->
|
||||
Mix.shell().error(inspect(err))
|
||||
Mix.raise("User has not been created because of an unknown reason.")
|
||||
shell_error(inspect(err))
|
||||
shell_error("User has not been created because of an unknown reason.")
|
||||
end
|
||||
end
|
||||
|
||||
def run(_) do
|
||||
Mix.raise("mobilizon.users.new requires an email as argument")
|
||||
shell_error("mobilizon.users.new requires an email as argument")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@ defmodule Mix.Tasks.Mobilizon.Users.Show do
|
|||
"""
|
||||
|
||||
use Mix.Task
|
||||
|
||||
import Mix.Tasks.Mobilizon.Common
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Users
|
||||
alias Mobilizon.Users.User
|
||||
|
@ -13,11 +13,11 @@ defmodule Mix.Tasks.Mobilizon.Users.Show do
|
|||
|
||||
@impl Mix.Task
|
||||
def run([email]) do
|
||||
Mix.Task.run("app.start")
|
||||
start_mobilizon()
|
||||
|
||||
with {:ok, %User{} = user} <- Users.get_user_by_email(email),
|
||||
actors <- Users.get_actors_for_user(user) do
|
||||
Mix.shell().info("""
|
||||
shell_info("""
|
||||
Informations for the user #{user.email}:
|
||||
- Activated: #{user.confirmed_at}
|
||||
- Disabled: #{user.disabled}
|
||||
|
@ -26,12 +26,12 @@ defmodule Mix.Tasks.Mobilizon.Users.Show do
|
|||
""")
|
||||
else
|
||||
{:error, :user_not_found} ->
|
||||
Mix.raise("Error: No such user")
|
||||
shell_error("Error: No such user")
|
||||
end
|
||||
end
|
||||
|
||||
def run(_) do
|
||||
Mix.raise("mobilizon.users.show requires an email as argument")
|
||||
shell_error("mobilizon.users.show requires an email as argument")
|
||||
end
|
||||
|
||||
defp display_actors([]), do: ""
|
||||
|
|
|
@ -21,7 +21,7 @@ defmodule Mobilizon do
|
|||
|
||||
@name Mix.Project.config()[:name]
|
||||
@version Mix.Project.config()[:version]
|
||||
@env Mix.env()
|
||||
@env Application.fetch_env!(:mobilizon, :env)
|
||||
|
||||
@spec named_version :: String.t()
|
||||
def named_version, do: "#{@name} #{@version}"
|
||||
|
|
53
lib/mobilizon/cli.ex
Normal file
53
lib/mobilizon/cli.ex
Normal file
|
@ -0,0 +1,53 @@
|
|||
# Portions of this file are derived from Pleroma:
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mobilizon.CLI do
|
||||
@moduledoc """
|
||||
CLI wrapper for releases
|
||||
"""
|
||||
alias Mix.Tasks.Mobilizon.Ecto.{Migrate, Rollback}
|
||||
|
||||
def run(args) do
|
||||
[task | args] = String.split(args)
|
||||
|
||||
case task do
|
||||
"migrate" -> migrate(args)
|
||||
"rollback" -> rollback(args)
|
||||
task -> mix_task(task, args)
|
||||
end
|
||||
end
|
||||
|
||||
defp mix_task(task, args) do
|
||||
Application.load(:mobilizon)
|
||||
{:ok, modules} = :application.get_key(:mobilizon, :modules)
|
||||
|
||||
module =
|
||||
Enum.find(modules, fn module ->
|
||||
module = Module.split(module)
|
||||
|
||||
case module do
|
||||
["Mix", "Tasks", "Mobilizon" | rest] ->
|
||||
String.downcase(Enum.join(rest, ".")) == task
|
||||
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end)
|
||||
|
||||
if module do
|
||||
module.run(args)
|
||||
else
|
||||
IO.puts("The task #{task} does not exist")
|
||||
end
|
||||
end
|
||||
|
||||
def migrate(args) do
|
||||
Migrate.run(args)
|
||||
end
|
||||
|
||||
def rollback(args) do
|
||||
Rollback.run(args)
|
||||
end
|
||||
end
|
|
@ -38,8 +38,7 @@ defmodule Mobilizon.Web.Endpoint do
|
|||
at: "/",
|
||||
from: {:mobilizon, "priv/static"},
|
||||
gzip: false,
|
||||
only:
|
||||
~w(index.html manifest.json service-worker.js css fonts images js favicon.ico robots.txt),
|
||||
only: ~w(index.html manifest.json service-worker.js css fonts img js favicon.ico robots.txt),
|
||||
only_matching: ["precache-manifest"]
|
||||
)
|
||||
|
||||
|
|
|
@ -169,7 +169,7 @@ defmodule Mobilizon.Web.Router do
|
|||
get("/:sig/:url/:filename", MediaProxyController, :remote)
|
||||
end
|
||||
|
||||
if Mix.env() in [:dev, :e2e] do
|
||||
if Application.fetch_env!(:mobilizon, :env) in [:dev, :e2e] do
|
||||
# If using Phoenix
|
||||
forward("/sent_emails", Bamboo.SentEmailViewerPlug)
|
||||
end
|
||||
|
|
41
rel/overlays/bin/mobilizon_ctl
Executable file
41
rel/overlays/bin/mobilizon_ctl
Executable file
|
@ -0,0 +1,41 @@
|
|||
#!/bin/sh
|
||||
# Portions of this file are derived from Pleroma:
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
if [ -z "$1" ] || [ "$1" = "help" ]; then
|
||||
echo "Usage: $(basename "$0") COMMAND [ARGS]
|
||||
|
||||
The known commands are:
|
||||
|
||||
migrate
|
||||
Execute database migrations (needs to be done after updates)
|
||||
|
||||
rollback [VERSION]
|
||||
Rollback database migrations (needs to be done before downgrading)
|
||||
|
||||
and any mix tasks under Mobilizon namespace, for example \`mix mobilizon.user.show COMMAND\` is
|
||||
equivalent to \`$(basename "$0") user.show COMMAND\`
|
||||
|
||||
By default mobilizon_ctl will try calling into a running instance to execute non migration-related commands,
|
||||
if for some reason this is undesired, set MOBILIZON_CTL_RPC_DISABLED environment variable.
|
||||
|
||||
"
|
||||
else
|
||||
SCRIPT=$(readlink -f "$0")
|
||||
SCRIPTPATH=$(dirname "$SCRIPT")
|
||||
|
||||
FULL_ARGS="$*"
|
||||
|
||||
ACTION="$1"
|
||||
if [ $# -gt 0 ]; then
|
||||
shift
|
||||
fi
|
||||
|
||||
if [ "$ACTION" = "migrate" ] || [ "$ACTION" = "rollback" ] || [ "$ACTION" = "create" ] || [ "$MOBILIZON_CTL_RPC_DISABLED" = true ]; then
|
||||
"$SCRIPTPATH"/mobilizon eval 'Mobilizon.CLI.run("'"$FULL_ARGS"'")'
|
||||
else
|
||||
"$SCRIPTPATH"/mobilizon rpc 'Mobilizon.CLI.run("'"$FULL_ARGS"'")'
|
||||
fi
|
||||
fi
|
|
@ -48,7 +48,9 @@ defmodule Mix.Tasks.Mobilizon.ActorsTest do
|
|||
end
|
||||
|
||||
test "show non-existing actor" do
|
||||
assert_raise Mix.Error, "Error: No such actor", fn -> Show.run([@username]) end
|
||||
Show.run([@username])
|
||||
assert_received {:mix_shell, :error, [message]}
|
||||
assert message =~ "Error: No such actor"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,6 +9,7 @@ defmodule Mix.Tasks.Mobilizon.RelayTest do
|
|||
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.{Actor, Follower}
|
||||
alias Mix.Tasks.Mobilizon.Relay.{Follow, Unfollow}
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub.Relay
|
||||
|
||||
|
@ -17,7 +18,7 @@ defmodule Mix.Tasks.Mobilizon.RelayTest do
|
|||
use_cassette "relay/fetch_relay_follow" do
|
||||
target_instance = "mobilizon1.com"
|
||||
|
||||
Mix.Tasks.Mobilizon.Relay.run(["follow", target_instance])
|
||||
Follow.run([target_instance])
|
||||
|
||||
local_actor = Relay.get_actor()
|
||||
assert local_actor.url =~ "/relay"
|
||||
|
@ -35,7 +36,7 @@ defmodule Mix.Tasks.Mobilizon.RelayTest do
|
|||
use_cassette "relay/fetch_relay_unfollow" do
|
||||
target_instance = "mobilizon1.com"
|
||||
|
||||
Mix.Tasks.Mobilizon.Relay.run(["follow", target_instance])
|
||||
Follow.run([target_instance])
|
||||
|
||||
%Actor{} = local_actor = Relay.get_actor()
|
||||
|
||||
|
@ -44,7 +45,7 @@ defmodule Mix.Tasks.Mobilizon.RelayTest do
|
|||
|
||||
assert %Follower{} = Actors.is_following(local_actor, target_actor)
|
||||
|
||||
Mix.Tasks.Mobilizon.Relay.run(["unfollow", target_instance])
|
||||
Unfollow.run([target_instance])
|
||||
|
||||
refute Actors.is_following(local_actor, target_actor)
|
||||
end
|
||||
|
|
|
@ -42,9 +42,15 @@ defmodule Mix.Tasks.Mobilizon.UsersTest do
|
|||
test "create with already used email" do
|
||||
insert(:user, email: @email)
|
||||
|
||||
assert_raise Mix.Error, "User has not been created because of the above reason.", fn ->
|
||||
New.run([@email])
|
||||
end
|
||||
# Debug message
|
||||
assert_received {:mix_shell, :error, [message]}
|
||||
|
||||
assert message =~
|
||||
"[email: {\"This email is already used.\", [constraint: :unique, constraint_name: \"users_email_index\"]}]"
|
||||
|
||||
assert_received {:mix_shell, :error, [message]}
|
||||
assert message =~ "User has not been created because of the above reason."
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -62,7 +68,9 @@ defmodule Mix.Tasks.Mobilizon.UsersTest do
|
|||
end
|
||||
|
||||
test "delete non-existing user" do
|
||||
assert_raise Mix.Error, "Error: No such user", fn -> Delete.run([@email, "-y"]) end
|
||||
Delete.run([@email, "-y"])
|
||||
assert_received {:mix_shell, :error, [message]}
|
||||
assert message =~ "Error: No such user"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -87,7 +95,9 @@ defmodule Mix.Tasks.Mobilizon.UsersTest do
|
|||
end
|
||||
|
||||
test "show non-existing user" do
|
||||
assert_raise Mix.Error, "Error: No such user", fn -> Show.run([@email]) end
|
||||
Show.run([@email])
|
||||
assert_received {:mix_shell, :error, [message]}
|
||||
assert message =~ "Error: No such user"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -160,11 +170,9 @@ defmodule Mix.Tasks.Mobilizon.UsersTest do
|
|||
end
|
||||
|
||||
test "enable and disable at the same time" do
|
||||
assert_raise Mix.Error,
|
||||
"Can't use both --enabled and --disable options at the same time.",
|
||||
fn ->
|
||||
Modify.run([@email, "--disable", "--enable"])
|
||||
end
|
||||
assert_received {:mix_shell, :error, [message]}
|
||||
assert message =~ "Can't use both --enabled and --disable options at the same time."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue