replace multi-part config system with runtime.exs

also bump version number
This commit is contained in:
Eric S. Londres 2022-12-01 22:45:15 -05:00
parent c979b2c14b
commit 82c52f21bf
Signed by: slondr
GPG Key ID: A2D25B4D5CB970E4
10 changed files with 43 additions and 96 deletions

View File

@ -1,15 +1,18 @@
import Config
# Elixir releases really don't play well with command-line arguments, but can import env variables like so.
config :geminex,
port: System.get_env("gemini_port", "1965") |> Integer.parse() |> elem(0),
keyfile: System.get_env("keyfile", "server.key"),
certfile: System.get_env("certfile", "server.crt")
config :diarchy,
port: System.get_env("spartan_port", "3000") |> Integer.parse() |> elem(0)
config :egalaxyd,
root_dir: System.get_env("root_dir", Path.join(File.cwd!(), "public")),
single_file_mode: System.get_env("single_file") == "true",
follow_symlinks: System.get_env("symlinks") == "true",
allow_updir: System.get_env("updir") == "true",
allow_secrets: System.get_env("serve_secrets") == "true",
path_seperator: System.get_env("path_seperator", "/")
config :egalaxyd, :env_opts, %{
port: System.get_env("port"),
root_dir: System.get_env("root_dir"),
single_file_mode: System.get_env("single_file"),
keyfile: System.get_env("keyfile"),
certfile: System.get_env("certfile"),
follow_symlinks: System.get_env("symlinks"),
allow_updir: System.get_env("updir"),
allow_secrets: System.get_env("serve_secrets"),
path_seperator: System.get_env("path_seperator")
}

View File

@ -10,28 +10,10 @@ defmodule Egalaxyd.ConfigSentinel do
@moduledoc """
Sets up and then guards the global config.
To add a new config option:
1. set a default in default_opts
2. define the type in opt_types
3. add the default as a comment in geminex.toml (to prompt users to customize it)
To add a new config option, define it in config/runtime.exs
"""
@opt_types %{
gemini_port: :integer,
spartan_port: :integer,
root_dir: :string,
single_file_mode: :boolean,
keyfile: :string,
certfile: :string,
follow_symlinks: :boolean,
allow_updir: :boolean,
allow_secrets: :boolean,
path_seperator: :string
}
# Client functions
def start_link(opts) do
def start_link(_opts) do
GenServer.start_link(__MODULE__, %{})
end
@ -39,22 +21,22 @@ defmodule Egalaxyd.ConfigSentinel do
Returns the value of the given option.
Examples
iex> Geminex.ConfigSentinel.getopt(:gemini_port)
iex> Geminex.ConfigSentinel.getopt(:geminex, :port)
1965
"""
def getopt(opt) do
GenServer.call(:config_sentinel, opt)
def getopt(app, opt) do
GenServer.call(:config_sentinel, {app, opt})
end
@doc """
Sets the given `opt` to the given `value`.
Examples
iex> Geminex.ConfigSentinel.setopt(:allow_updir, true)
iex> Geminex.ConfigSentinel.setopt(:egalaxyd, :allow_updir, true)
:ok
"""
def setopt(opt, optval) do
GenServer.cast(:config_sentinel, {opt, optval})
def setopt(app, opt, optval) do
GenServer.cast(:config_sentinel, {app, opt, optval})
end
# Server callbacks
@ -64,54 +46,17 @@ defmodule Egalaxyd.ConfigSentinel do
unless Process.whereis(:config_sentinel) == nil, do: Process.unregister(:config_sentinel)
Process.register(self(), :config_sentinel)
{:ok, Map.merge(state, default_opts) |> Map.merge(opts_from_conf) |> Map.merge(opts_from_env)}
{:ok, state}
end
@impl true
def handle_call(opt, _from, state) do
{:reply, Map.get(state, opt), state}
def handle_call({app, opt}, _from, state) do
{:reply, Application.fetch_env!(app, opt), state}
end
@impl true
def handle_cast({optname, optval}, state) do
{:noreply, Map.put(state, optname, optval)}
end
# Helper functions
defp default_opts do
%{
gemini_port: 1965,
spartan_port: 3000, # Technically spartan should listen on port 300, but that is privileged
root_dir: File.cwd!,
single_file_mode: false,
keyfile: "server.key",
certfile: "server.crt",
follow_symlinks: false,
allow_updir: false,
allow_secrets: false,
path_seperator: "/" # temporary workaround for windows support
}
end
defp opts_from_conf do
case File.read("config.toml") do
{:ok, config_toml_data} -> Toml.decode!(config_toml_data, keys: :atoms)
_ -> %{}
end
end
defp opts_from_env do
# Users can specify options via environment variables. This is handled by config/runtime.exs
# Non-specified options are nil
:erlang.element(2, Application.get_all_env(:egalaxyd) |> List.first())
|> Map.filter(fn {_,v} -> v != nil end) # remove all values that were not specified
|> Map.map(fn {k,v} ->
case Map.get(@opt_types, k) do
:string -> v
:boolean -> if v == "false" || v == "0", do: false, else: true
:integer -> String.trim(v) |> String.to_integer()
end
end)
def handle_cast({app, optname, optval}, state) do
Application.put_env(app, optname, optval)
{:noreply, state}
end
end

View File

@ -14,7 +14,7 @@ defmodule Diarchy.Application do
@impl true
def start(_type, _args) do
port = (System.get_env("PORT") || "3000") |> String.to_integer() # TODO: Use ConfigSentinel instead of this
port = Egalaxyd.ConfigSentinel.getopt(:diarchy, :port)
children = [
# Starts a worker by calling: Diarchy.Worker.start_link(arg)

View File

@ -63,7 +63,7 @@ defmodule Diarchy.Server do
rescue
e ->
Logger.error(Exception.format(:error, e, __STACKTRACE__))
%Diarchy.Response{status: 5, type: "internal server error"}
%Response{status: 5, type: "internal server error"}
end
end

View File

@ -25,7 +25,7 @@ defmodule Egalaxyd.FileRetrieval do
{:error, :bad_request}
end
prefixed_path = ConfigSentinel.getopt(:root_dir)
prefixed_path = ConfigSentinel.getopt(:egalaxyd, :root_dir)
|> Path.absname()
|> (&(if String.starts_with?(path, &1), do: path, else: Path.join(&1, path))).()
@ -43,13 +43,13 @@ defmodule Egalaxyd.FileRetrieval do
@doc "Check request to ensure secrets are not requests, if the setting is enabled"
defp request_references_forbidden_secrets?(path) do
keyfile = ConfigSentinel.getopt(:keyfile)
certfile = ConfigSentinel.getopt(:certfile)
(not ConfigSentinel.getopt(:allow_secrets)) && ((String.match?(path, ~r/#{keyfile}/)) || (String.match?(path, ~r/#{certfile}/)))
keyfile = ConfigSentinel.getopt(:geminex, :keyfile)
certfile = ConfigSentinel.getopt(:geminex, :certfile)
(not ConfigSentinel.getopt(:egalaxyd, :allow_secrets)) && ((String.match?(path, ~r/#{keyfile}/)) || (String.match?(path, ~r/#{certfile}/)))
end
@doc "Check request to ensure directory traversal is not attempted, if the setting is enabled"
defp request_attempts_to_escape?(path) do
(not ConfigSentinel.getopt(:allow_updir)) && String.match?(path, ~r/\.\./)
(not ConfigSentinel.getopt(:egalaxyd, :allow_updir)) && String.match?(path, ~r/\.\./)
end
end

View File

@ -51,8 +51,7 @@ defmodule Geminex.RequestParser do
req.scheme == nil ->
# No scheme was provided
response("59 bad request\r\n")
req.scheme != "gemini" || (req.port != 1965 && req.port != nil) ->
# TODO: use the config option here
req.scheme != "gemini" || (req.port != ConfigSentinel.getopt(:geminex, :port) && req.port != nil) ->
response("53 protocol refused\r\n") # this is a gemini server!
true ->
# todo: mime typing

View File

@ -21,9 +21,9 @@ defmodule Geminex.SecureSocketMaster do
def init(:ok) do
Logger.info("Initializing gemini server.")
gemini_port = ConfigSentinel.getopt(:gemini_port)
keyfile = ConfigSentinel.getopt(:keyfile)
certfile = ConfigSentinel.getopt(:certfile)
gemini_port = ConfigSentinel.getopt(:geminex, :port)
keyfile = ConfigSentinel.getopt(:geminex, :keyfile)
certfile = ConfigSentinel.getopt(:geminex, :certfile)
# begin listening on the TLS port
{:ok, listen_socket} = SSL.listen(gemini_port, [

View File

@ -19,7 +19,7 @@ defmodule Egalaxyd.Supervisor do
{Egalaxyd.ConfigSentinel, %{}},
{Geminex.RequestParser, []},
{Task.Supervisor, name: Diarchy.ServerSupervisor},
Supervisor.child_spec({Task, fn -> Diarchy.Server.accept(Egalaxyd.ConfigSentinel.getopt(:spartan_port)) end}, restart: :permanent),
Supervisor.child_spec({Task, fn -> Diarchy.Server.accept(Egalaxyd.ConfigSentinel.getopt(:diarchy, :port)) end}, restart: :permanent),
Geminex.SecureSocketMaster
]

View File

@ -4,7 +4,7 @@ defmodule Egalaxyd.MixProject do
def project do
[
app: :egalaxyd,
version: "0.5.3-alpha0",
version: "0.6.0",
elixir: "~> 1.13",
start_permanent: Mix.env() == :prod,
deps: deps(),

View File

@ -8,7 +8,7 @@ defmodule DiarchyServerTest do
end
test "read_file reads file", context do
root_dir = Egalaxyd.ConfigSentinel.getopt(:root_dir)
root_dir = Egalaxyd.ConfigSentinel.getopt(:egalaxyd, :root_dir)
file_name = Path.join(root_dir, to_string(:rand.uniform())) |> Path.absname()
file_data = :rand.uniform() |> to_string()
File.write!(file_name, file_data)