import Config require Logger require Keila :ok = Application.ensure_started(:logger) {:ok, _} = Application.ensure_all_started(:tls_certificate_check) exit_from_exception = fn exception, message -> Logger.error(Exception.message(exception)) Logger.error(message) Logger.flush() System.halt(1) end maybe_to_int = fn string when string not in [nil, ""] -> String.to_integer(string) _ -> nil end put_if_not_empty = fn enumerable, key, value when value not in [nil, ""] -> put_in(enumerable, [key], value) enumerable, _, _ -> enumerable end if config_env() == :prod do # Database try do db_url = System.fetch_env!("DB_URL") ssl = System.get_env("DB_ENABLE_SSL") in [1, "1", "true", "TRUE"] ssl_opts = [] |> then(fn opts -> verify_peer? = System.get_env("DB_VERIFY_SSL_HOST", "TRUE") in [1, "1", "true", "TRUE"] if verify_peer? do Keyword.put(opts, :verify, :verify_peer) else Keyword.put(opts, :verify, :verify_none) end end) |> then(fn opts -> ca_cert_pem = System.get_env("DB_CA_CERT") cacerts = if ca_cert_pem not in [nil, ""] do ca_cert_pem |> :public_key.pem_decode() |> Enum.map(fn {_, der_or_encrypted_der, _} -> der_or_encrypted_der end) end if cacerts do Keyword.put(opts, :cacerts, cacerts) else opts end end) # Database prefix db_schema = System.get_env("DB_SCHEMA") if db_schema not in [nil, ""] do config :keila, Keila.Repo, migration_default_prefix: db_schema, parameters: [search_path: db_schema] config :keila, Oban, prefix: db_schema end config :keila, Keila.Repo, url: db_url, ssl: ssl, ssl_opts: ssl_opts rescue e -> exit_from_exception.(e, """ You must provide the DB_URL environment variable in the format: postgres://user:password/database """) end # System Mailer try do mailer_type = System.get_env("MAILER_TYPE") || "smtp" tls_mode = case System.get_env("MAILER_SMTP_TLS_MODE", "") |> String.downcase() do "tls" -> :tls "starttls" -> :starttls "none" -> :none "auto" -> :default "" -> cond do System.get_env("MAILER_ENABLE_SSL", "FALSE") in ["1", "true", "TRUE"] -> :tls System.get_env("MAILER_ENABLE_STARTTLS") in ["1", "true", "TRUE"] -> :starttls true -> :default end end auth_method = case System.get_env("MAILER_SMTP_AUTH_METHOD", "auto") |> String.downcase() do "password" -> :password "none" -> :none "auto" -> :default end config = case mailer_type do "smtp" -> host = System.fetch_env!("MAILER_SMTP_HOST") from_email = System.fetch_env!("MAILER_SMTP_FROM_EMAIL") user = System.get_env("MAILER_SMTP_USER") || from_email password = if auth_method != :none, do: System.fetch_env!("MAILER_SMTP_PASSWORD") port = System.get_env("MAILER_SMTP_PORT", "587") |> maybe_to_int.() [ adapter: Swoosh.Adapters.SMTP, relay: host, from_email: from_email ] |> put_if_not_empty.(:username, user) |> put_if_not_empty.(:password, password) |> put_if_not_empty.(:port, port) |> then(fn config -> case auth_method do :password -> Keyword.put(config, :auth, :always) :none -> Keyword.put(config, :auth, :never) :default -> config end end) |> then(fn config -> case tls_mode do :tls -> config |> Keyword.put(:ssl, true) |> Keyword.put(:sockopts, :tls_certificate_check.options(host)) :starttls -> config |> Keyword.put(:tls, :always) |> Keyword.put( :tls_options, :tls_certificate_check.options(host) ++ [versions: [:"tlsv1.2"]] ) :none -> config |> Keyword.put(:tls, :never) |> Keyword.put(:ssl, false) :default -> config end end) end config(:keila, Keila.Auth.Emails, config) rescue e -> exit_from_exception.(e, """ You must configure a mailer for system emails. Use the following environment variables: - MAILER_TYPE (defaults to "smtp") - MAILER_SMTP_HOST (required) - MAILER_SMTP_USER - MAILER_SMTP_PASSWORD (required unless MAILER_SMTP_AUTH_METHOD=none) - MAILER_SMTP_PORT (optional, defaults to 587) - MAILER_SMTP_AUTH_METHOD (optional, defaults to "auto", options: "password", "none", "auto") - MAILER_SMTP_TLS_MODE (optional, defaults to "auto", options: "tls", "starttls", "none", "auto") """) end # Captcha captcha_site_key = System.get_env("CAPTCHA_SITE_KEY") || System.get_env("HCAPTCHA_SITE_KEY") captcha_secret_key = System.get_env("CAPTCHA_SECRET_KEY") || System.get_env("HCAPTCHA_SECRET_KEY") captcha_verify_url = System.get_env("CAPTCHA_VERIFY_URL") || System.get_env("CAPTCHA_URL") || System.get_env("HCAPTCHA_URL") captcha_script_url = System.get_env("CAPTCHA_SCRIPT_URL") if captcha_site_key not in [nil, ""] and captcha_secret_key not in [nil, ""] do captcha_provider = System.get_env("CAPTCHA_PROVIDER", "hcaptcha") |> String.downcase() |> case do "friendly_captcha" -> :friendly_captcha _other -> :hcaptcha end Logger.info("Using the #{captcha_provider} captcha provider") config = [ secret_key: captcha_secret_key, site_key: captcha_site_key, provider: captcha_provider ] |> put_if_not_empty.(:verify_url, captcha_verify_url) |> put_if_not_empty.(:script_url, captcha_script_url) config :keila, KeilaWeb.Captcha, config else Logger.warning(""" Captcha not configured. Keila will fall back to using hCaptcha’s staging configuration. To configure a captcha, use the following environment variables: - CAPTCHA_SITE_KEY - CAPTCHA_SECRET_KEY - CAPTCHA_VERIFY_URL (defaults to https://hcaptcha.com/siteverify or https://api.friendlycaptcha.com/api/v1/siteverify) - CAPTCHA_SCRIPT_URL (defaults to https://hcaptcha.com/1/api.js for hCaptcha or https://unpkg.com/friendly-challenge@0.9.11/widget.module.min.js for Friendly Captcha) - CAPTCHA_PROVIDER (defaults to hCaptcha, unless set to 'friendly_captcha') """) end # Secret Key Base try do secret_key_base = System.fetch_env!("SECRET_KEY_BASE") live_view_salt = :crypto.hash(:sha384, secret_key_base <> "live_view_salt") |> Base.url_encode64() config(:keila, KeilaWeb.Endpoint, secret_key_base: secret_key_base, live_view: [signing_salt: live_view_salt] ) rescue e -> exit_from_exception.(e, """ You must set SECRET_KEY_BASE. This should be a strong secret with a length of at least 64 characters. One way to create a strong secret is running the following command: head -c 48 /dev/urandom | base64 """) end # Hashids secret_key_base = Application.get_env(:keila, KeilaWeb.Endpoint) |> Keyword.fetch!(:secret_key_base) hashid_salt = case System.get_env("HASHID_SALT") do empty when empty in [nil, ""] -> Logger.warning(""" You have not configured a Hashid salt. Defaulting to :crypto.hash(:sha256, SECRET_KEY_BASE <> "hashid_salt") |> Base.url_encode64() """) :crypto.hash(:sha256, secret_key_base <> "hashid_salt") |> Base.url_encode64() salt -> salt end config(:keila, Keila.Id, salt: hashid_salt) # Main Endpoint url_host = System.get_env("URL_HOST") url_port = System.get_env("URL_PORT") |> maybe_to_int.() url_schema = System.get_env("URL_SCHEMA") url_path = System.get_env("URL_PATH") url_port = cond do url_port not in [nil, ""] -> url_port url_schema == "https" -> 443 true -> System.get_env("PORT") |> maybe_to_int.() || 4000 end url_schema = cond do url_schema not in [nil, ""] -> url_schema url_port == 443 -> "https" true -> "http" end if url_host not in [nil, ""] do config = [host: url_host, scheme: url_schema] |> put_if_not_empty.(:port, url_port) |> put_if_not_empty.(:path, url_path) config(:keila, KeilaWeb.Endpoint, url: config) else Logger.warning(""" You have not configured the application URL. Defaulting to http://localhost. Use the following environment variables: - URL_HOST - URL_PORT (defaults to PORT, or to 443 if URL_SCHEMA=https) - URL_SCHEMA (defaults to "https" for port 443, otherwise to "http") - URL_PATH (defaults to "/") """) end # File Storage user_content_dir = System.get_env("USER_CONTENT_DIR") default_user_content_dir = Application.get_env(:keila, Keila.Files.StorageAdapters.Local, []) |> Keyword.get(:dir) if user_content_dir not in [nil, ""] do config(:keila, Keila.Files.StorageAdapters.Local, dir: user_content_dir) else Logger.warning(""" You have not configured a directory for user uploads. Default directory "#{default_user_content_dir}" will be used. If want to store uploads in a different directory you can set USER_CONTENT_DIR """) end user_content_base_url = System.get_env("USER_CONTENT_BASE_URL") if user_content_base_url not in [nil, ""] do config(:keila, Keila.Files.StorageAdapters.Local, serve: false) config(:keila, Keila.Files.StorageAdapters.Local, base_url: user_content_base_url) else config(:keila, Keila.Files.StorageAdapters.Local, serve: true) Logger.warning(""" You have not configured a separate URL for untrusted content uploaded by users. If you serve user uploads on a different domain, you can set USER_CONTENT_BASE_URL """) end # Application Port port = System.get_env("PORT") |> maybe_to_int.() if not is_nil(port) do config(:keila, KeilaWeb.Endpoint, http: [port: port]) else Logger.info(""" PORT environment variable unset. Running on port 4000. """) end # Deployment config :keila, # Disable registration of new users via the UI registration_disabled: System.get_env("DISABLE_REGISTRATION") not in [nil, "", "0", "false", "FALSE"], # Disable creation of Senders not using SharedSenders. sender_creation_disabled: System.get_env("DISABLE_SENDER_CREATION") not in [nil, "", "0", "false", "FALSE"] # Enable sending quotas config :keila, Keila.Accounts, credits_enabled: System.get_env("ENABLE_QUOTAS") in [1, "1", "true", "TRUE"] # Enable update check config :keila, :update_checks_enabled, System.get_env("DISABLE_UPDATE_CHECKS") not in [1, "1", "true", "TRUE"] # Disable tz auto-update check if System.get_env("DISABLE_TZDATA_UPDATES") in [1, "1", "true", "TRUE"] do config :keila, :disable_tz_updates, true end # Precedence Bulk Header if System.get_env("DISABLE_PRECEDENCE_HEADER") in [1, "1", "true", "TRUE"] do config(:keila, Keila.Mailings, enable_precedence_header: false) end # Message body retention message_retention_days = System.get_env("MESSAGE_RETENTION_DAYS") |> maybe_to_int.() if message_retention_days do config :keila, Keila.Mailings, message_retention_days: message_retention_days end # Default to info messages in production case System.get_env("LOG_LEVEL") do level when level in ["info", "error", "debug"] -> config :logger, level: String.to_existing_atom(level) _ -> config :logger, level: :info end end if config_env() == :test do db_url = System.get_env("DB_URL") if db_url do db_url = db_url <> "#{System.get_env("MIX_TEST_PARTITION")}" config(:keila, Keila.Repo, url: db_url) end end Keila.if_cloud do use KeilaCloud.RuntimeConfig end