Page MenuHomePhorge

No OneTemporary

Size
20 KB
Referenced Files
None
Subscribers
None
diff --git a/docs/administration/CLI_tasks/frontend.md b/docs/administration/CLI_tasks/frontend.md
index d4a48cb56..6a330975d 100644
--- a/docs/administration/CLI_tasks/frontend.md
+++ b/docs/administration/CLI_tasks/frontend.md
@@ -1,96 +1,113 @@
# Managing frontends
=== "OTP"
```sh
- ./bin/pleroma_ctl frontend install <frontend> [--ref <ref>] [--file <file>] [--build-url <build-url>] [--path <path>] [--build-dir <build-dir>]
+ ./bin/pleroma_ctl frontend install <frontend> [--ref <ref>] [--file <file>] [--build-url <build-url>] [--path <path>] [--build-dir <build-dir>] [--primary] [--admin]
+ ./bin/pleroma_ctl frontend enable <frontend> [--ref <ref>] [--file <file>] [--build-url <build-url>] [--path <path>] [--build-dir <build-dir>] [--primary] [--admin]
```
=== "From Source"
```sh
- mix pleroma.frontend install <frontend> [--ref <ref>] [--file <file>] [--build-url <build-url>] [--path <path>] [--build-dir <build-dir>]
+ mix pleroma.frontend install <frontend> [--ref <ref>] [--file <file>] [--build-url <build-url>] [--path <path>] [--build-dir <build-dir>] [--primary] [--admin]
+ mix pleroma.frontend enable <frontend> [--ref <ref>] [--file <file>] [--build-url <build-url>] [--path <path>] [--build-dir <build-dir>] [--primary] [--admin]
```
Frontend can be installed either from local zip file, or automatically downloaded from the web.
You can give all the options directly on the command line, but missing information will be filled out by looking at the data configured under `frontends.available` in the config files.
Currently, known `<frontend>` values are:
- [admin-fe](https://git.pleroma.social/pleroma/admin-fe)
- [kenoma](http://git.pleroma.social/lambadalambda/kenoma)
- [pleroma-fe](http://git.pleroma.social/pleroma/pleroma-fe)
- [fedi-fe](https://git.pleroma.social/pleroma/fedi-fe)
- [soapbox-fe](https://gitlab.com/soapbox-pub/soapbox-fe)
You can still install frontends that are not configured, see below.
## Example installations for a known frontend
For a frontend configured under the `available` key, it's enough to install it by name.
=== "OTP"
```sh
./bin/pleroma_ctl frontend install pleroma
```
=== "From Source"
```sh
mix pleroma.frontend install pleroma
```
This will download the latest build for the pre-configured `ref` and install it. It can then be configured as the one of the served frontends in the config file (see `primary` or `admin`).
You can override any of the details. To install a pleroma build from a different URL, you could do this:
=== "OTP"
```sh
./bin/pleroma_ctl frontend install pleroma --ref 2hu_edition --build-url https://example.org/raymoo.zip
```
=== "From Source"
```sh
mix pleroma.frontend install pleroma --ref 2hu_edition --build-url https://example.org/raymoo.zip
```
Similarly, you can also install from a local zip file.
=== "OTP"
```sh
./bin/pleroma_ctl frontend install pleroma --ref mybuild --file ~/Downloads/doomfe.zip
```
=== "From Source"
```sh
mix pleroma.frontend install pleroma --ref mybuild --file ~/Downloads/doomfe.zip
```
The resulting frontend will always be installed into a folder of this template: `${instance_static}/frontends/${name}/${ref}`.
Careful: This folder will be completely replaced on installation.
## Example installation for an unknown frontend
The installation process is the same, but you will have to give all the needed options on the command line. For example:
=== "OTP"
```sh
./bin/pleroma_ctl frontend install gensokyo --ref master --build-url https://gensokyo.2hu/builds/marisa.zip
```
=== "From Source"
```sh
mix pleroma.frontend install gensokyo --ref master --build-url https://gensokyo.2hu/builds/marisa.zip
```
If you don't have a zip file but just want to install a frontend from a local path, you can simply copy the files over a folder of this template: `${instance_static}/frontends/${name}/${ref}`.
+## Enabling a frontend
+
+Once installed, a frontend can be enabled with the `enable` command:
+
+=== "OTP"
+
+ ```sh
+ ./bin/pleroma_ctl frontend enable gensokyo --primary
+ ```
+
+=== "From Source"
+
+ ```sh
+ mix pleroma.frontend enable gensokyo --primary
+ ```
diff --git a/lib/mix/tasks/pleroma/frontend.ex b/lib/mix/tasks/pleroma/frontend.ex
index 9b151c3bd..819cd9f6a 100644
--- a/lib/mix/tasks/pleroma/frontend.ex
+++ b/lib/mix/tasks/pleroma/frontend.ex
@@ -1,44 +1,99 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Frontend do
use Mix.Task
import Mix.Pleroma
alias Pleroma.Frontend
@shortdoc "Manages bundled Pleroma frontends"
@moduledoc File.read!("docs/administration/CLI_tasks/frontend.md")
def run(["install", "none" | _args]) do
shell_info("Skipping frontend installation because none was requested")
"none"
end
def run(["install", name | args]) do
start_pleroma()
{options, [], []} =
OptionParser.parse(
args,
strict: [
ref: :string,
build_url: :string,
build_dir: :string,
file: :string
]
)
- options
- |> Keyword.put(:name, name)
- |> opts_to_frontend()
- |> Frontend.install()
+ shell_info("Installing frontend #{name}...")
+
+ with %Frontend{} = fe <-
+ options
+ |> Keyword.put(:name, name)
+ |> opts_to_frontend()
+ |> Frontend.install() do
+ shell_info("Frontend #{fe.name} installed")
+ else
+ error ->
+ shell_error("Failed to install frontend")
+ exit(inspect(error))
+ end
+ end
+
+ def run(["enable", name | args]) do
+ start_pleroma()
+
+ {options, [], []} =
+ OptionParser.parse(
+ args,
+ strict: [
+ ref: :string,
+ build_url: :string,
+ build_dir: :string,
+ file: :string,
+ admin: :boolean,
+ primary: :boolean
+ ]
+ )
+
+ frontend_type = get_frontend_type(options)
+
+ shell_info("Enabling frontend #{name}...")
+
+ with %Frontend{} = fe <-
+ options
+ |> Keyword.put(:name, name)
+ |> opts_to_frontend()
+ |> Frontend.enable(frontend_type) do
+ shell_info("Frontend #{fe.name} enabled")
+ else
+ error ->
+ shell_error("Failed to enable frontend")
+ exit(inspect(error))
+ end
end
defp opts_to_frontend(opts) do
struct(Frontend, opts)
end
+
+ defp get_frontend_type(opts) do
+ case Enum.into(opts, %{}) do
+ %{admin: true, primary: true} ->
+ raise "Invalid command. Only one frontend type may be selected."
+
+ %{admin: true} ->
+ :admin
+
+ _ ->
+ :primary
+ end
+ end
end
diff --git a/lib/pleroma/frontend.ex b/lib/pleroma/frontend.ex
index a0d496193..cd49a4899 100644
--- a/lib/pleroma/frontend.ex
+++ b/lib/pleroma/frontend.ex
@@ -1,146 +1,184 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Frontend do
alias Pleroma.Config
+ alias Pleroma.ConfigDB
alias Pleroma.Frontend
require Logger
@unknown_name "unknown"
+ @frontend_types [:admin, :primary]
defstruct [:name, :ref, :git, :build_url, :build_dir, :file, :"custom-http-headers"]
def install(%Frontend{} = frontend) do
frontend
|> maybe_put_name()
|> hydrate()
|> validate!()
|> do_install()
end
defp maybe_put_name(%{name: nil} = fe), do: Map.put(fe, :name, @unknown_name)
defp maybe_put_name(fe), do: fe
# Merges a named frontend with the provided one
defp hydrate(%Frontend{name: name} = frontend) do
get_named_frontend(name)
|> merge(frontend)
end
defp do_install(%Frontend{ref: ref, name: name} = frontend) do
dest = Path.join([dir(), name, ref])
label = "#{name} (#{ref})"
tmp_dir = Path.join(dir(), "tmp")
with {_, :ok} <- {:download_or_unzip, download_or_unzip(frontend, tmp_dir)},
Logger.info("Installing #{label} to #{dest}"),
:ok <- install_frontend(frontend, tmp_dir, dest) do
File.rm_rf!(tmp_dir)
Logger.info("Frontend #{label} installed to #{dest}")
+ frontend
else
{:download_or_unzip, _} ->
Logger.info("Could not download or unzip the frontend")
{:error, "Could not download or unzip the frontend"}
_e ->
Logger.info("Could not install the frontend")
{:error, "Could not install the frontend"}
end
end
+ def enable(%Frontend{} = frontend, frontend_type) when frontend_type in @frontend_types do
+ with {:config_db, true} <- {:config_db, Config.get(:configurable_from_database)} do
+ frontend
+ |> maybe_put_name()
+ |> hydrate()
+ |> validate!()
+ |> do_enable(frontend_type)
+ else
+ {:config_db, _} ->
+ map = to_map(frontend)
+
+ raise """
+ Can't enable frontend; database configuration is disabled.
+ Enable the frontend by manually adding this line to your config:
+
+ config :pleroma, :frontends, #{to_string(frontend_type)}: #{inspect(map)}
+
+ Alternatively, enable database configuration:
+
+ config :pleroma, configurable_from_database: true
+ """
+ end
+ end
+
+ def do_enable(%Frontend{name: name} = frontend, frontend_type) do
+ value = Keyword.put([], frontend_type, to_map(frontend))
+ params = %{group: :pleroma, key: :frontends, value: value}
+
+ with {:ok, _} <- ConfigDB.update_or_create(params),
+ :ok <- Config.TransferTask.load_and_update_env([], false) do
+ Logger.info("Frontend #{name} successfully enabled")
+ frontend
+ end
+ end
+
def dir do
Config.get!([:instance, :static_dir])
|> Path.join("frontends")
end
defp download_or_unzip(%Frontend{file: nil} = frontend, dest),
do: download_build(frontend, dest)
defp download_or_unzip(%Frontend{file: file}, dest) do
with {:ok, zip} <- File.read(Path.expand(file)) do
unzip(zip, dest)
end
end
def unzip(zip, dest) do
with {:ok, unzipped} <- :zip.unzip(zip, [:memory]) do
File.rm_rf!(dest)
File.mkdir_p!(dest)
Enum.each(unzipped, fn {filename, data} ->
path = filename
new_file_path = Path.join(dest, path)
new_file_path
|> Path.dirname()
|> File.mkdir_p!()
File.write!(new_file_path, data)
end)
end
end
def parse_build_url(%Frontend{ref: ref, build_url: build_url}) do
String.replace(build_url, "${ref}", ref)
end
defp download_build(%Frontend{name: name} = frontend, dest) do
Logger.info("Downloading pre-built bundle for #{name}")
url = parse_build_url(frontend)
with {:ok, %{status: 200, body: zip_body}} <-
Pleroma.HTTP.get(url, [], pool: :media, recv_timeout: 120_000) do
unzip(zip_body, dest)
else
{:error, e} -> {:error, e}
e -> {:error, e}
end
end
defp install_frontend(%Frontend{} = frontend, source, dest) do
from = frontend.build_dir || "dist"
File.rm_rf!(dest)
File.mkdir_p!(dest)
File.cp_r!(Path.join([source, from]), dest)
:ok
end
# Converts a named frontend into a %Frontend{} struct
def get_named_frontend(name) do
[:frontends, :available, name]
|> Config.get(%{})
|> from_map()
end
def merge(%Frontend{} = fe1, %Frontend{} = fe2) do
Map.merge(fe1, fe2, fn _key, v1, v2 ->
# This only overrides things that are actually set
v1 || v2
end)
end
def validate!(%Frontend{ref: ref} = fe) when is_binary(ref), do: fe
def validate!(_), do: raise("No ref given or configured")
def from_map(frontend) when is_map(frontend) do
struct(Frontend, atomize_keys(frontend))
end
def to_map(%Frontend{} = frontend) do
frontend
|> Map.from_struct()
|> stringify_keys()
end
defp atomize_keys(map) do
Map.new(map, fn {k, v} -> {String.to_existing_atom(k), v} end)
end
defp stringify_keys(map) do
Map.new(map, fn {k, v} -> {to_string(k), v} end)
end
end
diff --git a/test/mix/tasks/pleroma/frontend_test.exs b/test/mix/tasks/pleroma/frontend_test.exs
index aa4b25ebb..db6a8a4dd 100644
--- a/test/mix/tasks/pleroma/frontend_test.exs
+++ b/test/mix/tasks/pleroma/frontend_test.exs
@@ -1,85 +1,110 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.FrontendTest do
use Pleroma.DataCase
alias Mix.Tasks.Pleroma.Frontend
import ExUnit.CaptureIO, only: [capture_io: 1]
@dir "test/frontend_static_test"
setup do
File.mkdir_p!(@dir)
clear_config([:instance, :static_dir], @dir)
on_exit(fn ->
File.rm_rf(@dir)
end)
end
test "it downloads and unzips a known frontend" do
clear_config([:frontends, :available], %{
"pleroma" => %{
"ref" => "fantasy",
"name" => "pleroma",
"build_url" => "http://gensokyo.2hu/builds/${ref}"
}
})
Tesla.Mock.mock(fn %{url: "http://gensokyo.2hu/builds/fantasy"} ->
%Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/frontend_dist.zip")}
end)
capture_io(fn ->
Frontend.run(["install", "pleroma"])
end)
assert File.exists?(Path.join([@dir, "frontends", "pleroma", "fantasy", "test.txt"]))
end
test "it also works given a file" do
clear_config([:frontends, :available], %{
"pleroma" => %{
"ref" => "fantasy",
"name" => "pleroma",
"build_dir" => ""
}
})
folder = Path.join([@dir, "frontends", "pleroma", "fantasy"])
previously_existing = Path.join([folder, "temp"])
File.mkdir_p!(folder)
File.write!(previously_existing, "yey")
assert File.exists?(previously_existing)
capture_io(fn ->
Frontend.run(["install", "pleroma", "--file", "test/fixtures/tesla_mock/frontend.zip"])
end)
assert File.exists?(Path.join([folder, "test.txt"]))
refute File.exists?(previously_existing)
end
test "it downloads and unzips unknown frontends" do
Tesla.Mock.mock(fn %{url: "http://gensokyo.2hu/madeup.zip"} ->
%Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/frontend.zip")}
end)
capture_io(fn ->
Frontend.run([
"install",
"unknown",
"--ref",
"baka",
"--build-url",
"http://gensokyo.2hu/madeup.zip",
"--build-dir",
""
])
end)
assert File.exists?(Path.join([@dir, "frontends", "unknown", "baka", "test.txt"]))
end
+
+ describe "enable" do
+ setup do
+ clear_config(:configurable_from_database, true)
+ end
+
+ test "enabling a primary frontend" do
+ capture_io(fn -> Frontend.run(["enable", "soapbox-fe"]) end)
+
+ primary = Pleroma.Config.get([:frontends, :primary])
+ assert primary["name"] == "soapbox-fe"
+ end
+
+ test "enabling an admin frontend" do
+ capture_io(fn -> Frontend.run(["enable", "soapbox-fe", "--admin"]) end)
+
+ primary = Pleroma.Config.get([:frontends, :admin])
+ assert primary["name"] == "soapbox-fe"
+ end
+
+ test "raise if configurable_from_database is disabled" do
+ clear_config(:configurable_from_database, false)
+ assert_raise(RuntimeError, fn -> Frontend.run(["enable", "soapbox-fe"]) end)
+ end
+ end
end
diff --git a/test/pleroma/frontend_test.exs b/test/pleroma/frontend_test.exs
index 634487844..3cd30d121 100644
--- a/test/pleroma/frontend_test.exs
+++ b/test/pleroma/frontend_test.exs
@@ -1,136 +1,162 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.FrontendTest do
use Pleroma.DataCase
alias Pleroma.Frontend
@dir "test/frontend_static_test"
setup do
File.mkdir_p!(@dir)
clear_config([:instance, :static_dir], @dir)
on_exit(fn ->
File.rm_rf(@dir)
end)
end
test "it downloads and unzips a known frontend" do
frontend = %Frontend{
ref: "fantasy",
name: "pleroma",
build_url: "http://gensokyo.2hu/builds/${ref}"
}
clear_config([:frontends, :available], %{"pleroma" => Frontend.to_map(frontend)})
Tesla.Mock.mock(fn %{url: "http://gensokyo.2hu/builds/fantasy"} ->
%Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/frontend_dist.zip")}
end)
Frontend.install(frontend)
assert File.exists?(Path.join([@dir, "frontends", "pleroma", "fantasy", "test.txt"]))
end
test "it also works given a file" do
frontend = %Frontend{
ref: "fantasy",
name: "pleroma",
build_dir: "",
file: "test/fixtures/tesla_mock/frontend.zip"
}
clear_config([:frontends, :available], %{"pleroma" => Frontend.to_map(frontend)})
folder = Path.join([@dir, "frontends", "pleroma", "fantasy"])
previously_existing = Path.join([folder, "temp"])
File.mkdir_p!(folder)
File.write!(previously_existing, "yey")
assert File.exists?(previously_existing)
Frontend.install(frontend)
assert File.exists?(Path.join([folder, "test.txt"]))
refute File.exists?(previously_existing)
end
test "it downloads and unzips unknown frontends" do
frontend = %Frontend{
ref: "baka",
build_url: "http://gensokyo.2hu/madeup.zip",
build_dir: ""
}
Tesla.Mock.mock(fn %{url: "http://gensokyo.2hu/madeup.zip"} ->
%Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/frontend.zip")}
end)
Frontend.install(frontend)
assert File.exists?(Path.join([@dir, "frontends", "unknown", "baka", "test.txt"]))
end
test "merge/2 only overrides nil values" do
fe1 = %Frontend{name: "pleroma"}
fe2 = %Frontend{name: "soapbox", ref: "fantasy"}
expected = %Frontend{name: "pleroma", ref: "fantasy"}
assert Frontend.merge(fe1, fe2) == expected
end
test "validate!/1 raises if :ref isn't set" do
fe = %Frontend{name: "pleroma"}
assert_raise(RuntimeError, fn -> Frontend.validate!(fe) end)
end
test "validate!/1 returns the frontend" do
fe = %Frontend{name: "pleroma", ref: "fantasy"}
assert Frontend.validate!(fe) == fe
end
test "from_map/1 parses a map into a %Frontend{} struct" do
map = %{"name" => "pleroma", "ref" => "fantasy"}
expected = %Frontend{name: "pleroma", ref: "fantasy"}
assert Frontend.from_map(map) == expected
end
test "to_map/1 returns the frontend as a map with string keys" do
frontend = %Frontend{name: "pleroma", ref: "fantasy"}
expected = %{
"name" => "pleroma",
"ref" => "fantasy",
"build_dir" => nil,
"build_url" => nil,
"custom-http-headers" => nil,
"file" => nil,
"git" => nil
}
assert Frontend.to_map(frontend) == expected
end
test "parse_build_url/1 replaces ${ref}" do
frontend = %Frontend{
name: "pleroma",
ref: "fantasy",
build_url: "http://gensokyo.2hu/builds/${ref}"
}
expected = "http://gensokyo.2hu/builds/fantasy"
assert Frontend.parse_build_url(frontend) == expected
end
test "dir/0 returns the frontend dir" do
assert Frontend.dir() == "test/frontend_static_test/frontends"
end
test "get_named_frontend/1 returns a frontend from the config" do
frontend = %Frontend{name: "pleroma", ref: "fantasy"}
clear_config([:frontends, :available], %{"pleroma" => Frontend.to_map(frontend)})
assert Frontend.get_named_frontend("pleroma") == frontend
end
+
+ describe "enable/2" do
+ setup do
+ clear_config(:configurable_from_database, true)
+ end
+
+ test "enables a primary frontend" do
+ frontend = %Frontend{name: "soapbox", ref: "v1.2.3"}
+ map = Frontend.to_map(frontend)
+
+ clear_config([:frontends, :available], %{"soapbox" => map})
+ Frontend.enable(frontend, :primary)
+
+ assert Pleroma.Config.get([:frontends, :primary]) == map
+ end
+
+ test "enables an admin frontend" do
+ frontend = %Frontend{name: "admin-fe", ref: "develop"}
+ map = Frontend.to_map(frontend)
+
+ clear_config([:frontends, :available], %{"admin-fe" => map})
+ Frontend.enable(frontend, :admin)
+
+ assert Pleroma.Config.get([:frontends, :admin]) == map
+ end
+ end
end

File Metadata

Mime Type
text/x-diff
Expires
Fri, Sep 18, 11:58 PM (8 h, 23 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1768495
Default Alt Text
(20 KB)

Event Timeline