replace multi-part config system with runtime.exs
also bump version number
This commit is contained in:
parent
c979b2c14b
commit
82c52f21bf
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, [
|
||||
|
|
|
@ -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
|
||||
]
|
||||
|
||||
|
|
2
mix.exs
2
mix.exs
|
@ -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(),
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue