From 3b2008bb9eab8e8f4a4379e5020a21cd5a506db4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aled=C5=BA?= Date: Mon, 18 Sep 2023 16:13:10 +0200 Subject: [PATCH] Refactor jellyfish configuration --- config/dev.exs | 6 +- config/prod.exs | 4 +- config/runtime.exs | 199 ++++++--------------------------- config/test.exs | 2 +- lib/jellyfish/config_reader.ex | 71 ++++++++++++ 5 files changed, 114 insertions(+), 168 deletions(-) create mode 100644 lib/jellyfish/config_reader.ex diff --git a/config/dev.exs b/config/dev.exs index 92e0057e..ab0e4a21 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -4,17 +4,17 @@ config :jellyfish, server_api_token: "development", dev_routes: true # For development, we disable any cache and enable # debugging and code reloading. -# We set ip and port in the runtime.exs to allow -# for spawning more thane one Jellyfish instance. # # The watchers configuration can be used to run external # watchers to your application. For example, we use it # with esbuild to bundle .js and .css sources. config :jellyfish, JellyfishWeb.Endpoint, + # Binding to loopback ipv4 address prevents access from other machines. + # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. + http: [ip: {127, 0, 0, 1}, port: 5002], check_origin: false, code_reloader: true, debug_errors: true, - secret_key_base: "eUQ+pDd8FmAwDrE4taJYgcYtaMqFMLygkVRBPfH8G98U1aveWB3Oa9TkOoehK61t", watchers: [] # ## SSL Support diff --git a/config/prod.exs b/config/prod.exs index 12e68b19..a934e9f2 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -8,7 +8,9 @@ import Config config :logger, level: :info # run the server automatically when using prod release -config :jellyfish, JellyfishWeb.Endpoint, server: true +config :jellyfish, JellyfishWeb.Endpoint, + http: [ip: {127, 0, 0, 1}, port: 8080], + server: true # Runtime production configuration, including reading # of environment variables, is done on config/runtime.exs. diff --git a/config/runtime.exs b/config/runtime.exs index 6229d77b..0d1f9115 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -1,74 +1,19 @@ import Config +alias Jellyfish.ConfigReader + # config/runtime.exs is executed for all environments, including # during releases. It is executed after compilation and before the # system starts, so it is typically used to load production configuration # and secrets from environment variables or elsewhere. Do not define # any compile-time configuration in here, as it won't be applied. # The block below contains prod specific runtime configuration. -defmodule ConfigParser do - def parse_integrated_turn_port_range(range) do - with [str1, str2] <- String.split(range, "-"), - from when from in 0..65_535 <- String.to_integer(str1), - to when to in from..65_535 and from <= to <- String.to_integer(str2) do - {from, to} - else - _else -> - raise(""" - Bad INTEGRATED_TURN_PORT_RANGE environment variable value. Expected "from-to", where `from` and `to` \ - are numbers between 0 and 65535 and `from` is not bigger than `to`, got: \ - #{inspect(range)} - """) - end - end - - def parse_ip(var_value, var_name) do - var_value = var_value |> to_charlist() - - case :inet.parse_address(var_value) do - {:ok, parsed_ip} -> - parsed_ip - - _error -> - with {:ok, parsed_ip} <- :inet.getaddr(var_value, :inet) do - parsed_ip - else - _error -> - raise(""" - Bad #{var_name} environment variable value. Expected valid ip address, got: #{inspect(var_value)} - """) - end - end - end - - def parse_port_number(nil, _var_name), do: nil - - def parse_port_number(var_value, var_name) do - with {port, _sufix} when port in 1..65535 <- Integer.parse(var_value) do - port - else - _var -> - raise( - "Bad #{var_name} environment variable value. Expected valid port number, got: #{inspect(var_value)}" - ) - end - end - - def get_env!(env_key) do - case System.get_env(env_key) do - nil -> raise("Environmental variable #{env_key} was not set properly") - env_val -> env_val - end - end -end +config :ex_dtls, impl: :nif +config :opentelemetry, traces_exporter: :none -hosts = - System.get_env("NODES", "") - |> String.split(" ") - |> Enum.reject(&(&1 == "")) - |> Enum.map(&String.to_atom(&1)) +hosts = ConfigReader.read_nodes("NODES") -unless Enum.empty?(hosts) do +if hosts do config :libcluster, topologies: [ epmd_cluster: [ @@ -81,122 +26,50 @@ end prod? = config_env() == :prod host = - case System.get_env("VIRTUAL_HOST") do - nil when prod? -> raise "Unset VIRTUAL_HOST environment variable" + case System.get_env("HOST") do + nil when prod? -> raise "Unset HOST environment variable" nil -> "localhost" other -> other end port = - case System.get_env("PORT") do - nil when prod? -> raise "Unset PORT environment variable" - nil -> 5002 - other -> String.to_integer(other) - end - -jellyfish_address = System.get_env("JELLYFISH_ADDRESS") || "#{host}:#{port}" - -config :ex_dtls, impl: :nif + ConfigReader.read_port("PORT") || + Application.get_env(:jellyfish, JellyfishWeb.Endpoint)[:http][:port] config :jellyfish, - webrtc_used: String.downcase(System.get_env("WEBRTC_USED", "true")) not in ["false", "f", "0"], - integrated_turn_ip: - System.get_env("INTEGRATED_TURN_IP", "127.0.0.1") - |> ConfigParser.parse_ip("INTEGRATED_TURN_IP"), - integrated_turn_listen_ip: - System.get_env("INTEGRATED_TURN_LISTEN_IP", "127.0.0.1") - |> ConfigParser.parse_ip("INTEGRATED_TURN_LISTEN_IP"), + webrtc_used: ConfigReader.read_boolean("WEBRTC_USED"), + integrated_turn_ip: ConfigReader.read_ip("INTEGRATED_TURN_IP") || {127, 0, 0, 1}, + integrated_turn_listen_ip: ConfigReader.read_ip("INTEGRATED_TURN_LISTEN_IP") || {127, 0, 0, 1}, integrated_turn_port_range: - System.get_env("INTEGRATED_TURN_PORT_RANGE", "50000-59999") - |> ConfigParser.parse_integrated_turn_port_range(), - integrated_turn_tcp_port: - System.get_env("INTEGRATED_TURN_TCP_PORT") - |> ConfigParser.parse_port_number("INTEGRATED_TURN_TCP_PORT"), + ConfigReader.read_port_range("INTEGRATED_TURN_PORT_RANGE") || {50_000, 59_999}, + integrated_turn_tcp_port: ConfigReader.read_port("INTEGRATED_TURN_TCP_PORT"), jwt_max_age: 24 * 3600, output_base_path: System.get_env("OUTPUT_BASE_PATH", "jellyfish_output") |> Path.expand(), - address: jellyfish_address, - metrics_ip: System.get_env("METRICS_IP", "127.0.0.1") |> ConfigParser.parse_ip("METRICS_IP"), - metrics_port: - System.get_env("METRICS_PORT", "9568") |> ConfigParser.parse_port_number("METRICS_PORT") + address: System.get_env("JELLYFISH_ADDRESS") || "#{host}:#{port}", + metrics_ip: ConfigReader.read_ip("METRICS_IP") || {127, 0, 0, 1}, + metrics_port: ConfigReader.read_port("METRICS_PORT") || 9568 -config :opentelemetry, traces_exporter: :none +config :jellyfish, JellyfishWeb.Endpoint, + secret_key_base: + System.get_env("SECRET_KEY_BASE") || Base.encode64(:crypto.strong_rand_bytes(48)), + http: [port: port] -case config_env() do - env when env != :prod -> - # we set ip and port here to allow for - # running multiple Jellyfishes in development - config :jellyfish, JellyfishWeb.Endpoint, - # Binding to loopback ipv4 address prevents access from other machines. - # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. - http: [ip: {127, 0, 0, 1}, port: port] +if check_origin = ConfigReader.read_boolean("CHECK_ORIGIN") do + config :jellyfish, JellyfishWeb.Endpoint, check_origin: check_origin +end - :prod -> - token = - System.fetch_env!("SERVER_API_TOKEN") || - raise """ - environment variable SERVER_API_TOKEN is missing. - SERVER_API_TOKEN is used for HTTP requests and - server WebSocket authorization. - """ +case System.get_env("SERVER_API_TOKEN") do + nil when prod? == true -> + raise """ + environment variable SERVER_API_TOKEN is missing. + SERVER_API_TOKEN is used for HTTP requests and + server WebSocket authorization. + """ + token -> config :jellyfish, server_api_token: token +end - # The secret key base is used to sign/encrypt cookies and other secrets. - # A default value is used in config/dev.exs and config/test.exs but you - # want to use a different value for prod and you most likely don't want - # to check this value into version control, so we use an environment - # variable instead. - secret_key_base = - System.get_env("SECRET_KEY_BASE") || - raise """ - environment variable SECRET_KEY_BASE is missing. - You can generate one by calling: mix phx.gen.secret - """ - - check_origin? = System.get_env("CHECK_ORIGIN", "true") == "true" - - config :jellyfish, JellyfishWeb.Endpoint, - url: [host: host, port: 443, scheme: "https"], - check_origin: check_origin?, - http: [ - # Enable IPv6 and bind on all interfaces. - # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. - # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html - # for details about using IPv6 vs IPv4 and loopback vs public addresses. - ip: {0, 0, 0, 0, 0, 0, 0, 0}, - port: port - ], - secret_key_base: secret_key_base - - # ## SSL Support - # - # To get SSL working, you will need to add the `https` key - # to your endpoint configuration: - # - # config :jellyfish, JellyfishWeb.Endpoint, - # https: [ - # ..., - # port: 443, - # cipher_suite: :strong, - # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), - # certfile: System.get_env("SOME_APP_SSL_CERT_PATH") - # ] - # - # The `cipher_suite` is set to `:strong` to support only the - # latest and more secure SSL ciphers. This means old browsers - # and clients may not be supported. You can set it to - # `:compatible` for wider support. - # - # `:keyfile` and `:certfile` expect an absolute path to the key - # and cert in disk or a relative path inside priv, for example - # "priv/ssl/server.key". For all supported SSL configuration - # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 - # - # We also recommend setting `force_ssl` in your endpoint, ensuring - # no data is ever sent via http, always redirecting to https: - # - # config :jellyfish, JellyfishWeb.Endpoint, - # force_ssl: [hsts: true] - # - # Check `Plug.SSL` for all available options in `force_ssl`. +if prod? do + config :jellyfish, JellyfishWeb.Endpoint, url: [host: host, port: 443, scheme: "https"] end diff --git a/config/test.exs b/config/test.exs index f38bc283..46bb0ab7 100644 --- a/config/test.exs +++ b/config/test.exs @@ -7,7 +7,7 @@ config :jellyfish, # We don't run a server during test. If one is required, # you can enable the server option below. config :jellyfish, JellyfishWeb.Endpoint, - secret_key_base: "DtVd7qfpae0tk5zRgAM75hOaCc+phk38gDFVvLPyqVN/vvVg0EPmksTSm5JcyjoJ", + http: [ip: {127, 0, 0, 1}, port: 4002], server: false # Print only warnings and errors during test diff --git a/lib/jellyfish/config_reader.ex b/lib/jellyfish/config_reader.ex new file mode 100644 index 00000000..9ab96351 --- /dev/null +++ b/lib/jellyfish/config_reader.ex @@ -0,0 +1,71 @@ +defmodule Jellyfish.ConfigReader do + @moduledoc false + + def read_port_range(env) do + if value = System.get_env(env) do + with [str1, str2] <- String.split(value, "-"), + from when from in 0..65_535 <- String.to_integer(str1), + to when to in from..65_535 and from <= to <- String.to_integer(str2) do + {from, to} + else + _else -> + raise(""" + Bad #{env} environment variable value. Expected "from-to", where `from` and `to` \ + are numbers between 0 and 65535 and `from` is not bigger than `to`, got: \ + #{value} + """) + end + end + end + + def read_ip(env) do + if value = System.get_env(env) do + value = value |> to_charlist() + + case :inet.parse_address(value) do + {:ok, parsed_ip} -> + parsed_ip + + _error -> + case :inet.getaddr(value, :inet) do + {:ok, parsed_ip} -> + parsed_ip + + _error -> + raise(""" + Bad #{env} environment variable value. Expected valid ip address, got: #{value}" + """) + end + end + end + end + + def read_port(env) do + if value = System.get_env(env) do + case Integer.parse(value) do + {port, _sufix} when port in 1..65535 -> + port + + _other -> + raise(""" + Bad #{env} environment variable value. Expected valid port number, got: #{value} + """) + end + end + end + + def read_nodes(env) do + if value = System.get_env(env) do + value + |> String.split(" ") + |> Enum.reject(&(&1 == "")) + |> Enum.map(&String.to_atom(&1)) + end + end + + def read_boolean(env) do + if value = System.get_env(env) do + String.downcase(value) not in ["false", "f", "0", nil] + end + end +end