| defmodule Plausible.Application do |
| @moduledoc false |
|
|
| use Application |
| use Plausible |
|
|
| def start(_type, _args) do |
| on_ee(do: Plausible.License.ensure_valid_license()) |
| on_ce(do: :inet_db.set_tcp_module(:happy_tcp)) |
|
|
| |
| endpoint = on_ee(do: PlausibleWeb.Endpoint, else: maybe_https_endpoint()) |
|
|
| cluster = |
| on_ee( |
| do: |
| {Cluster.Supervisor, |
| [ |
| [ |
| default: [ |
| strategy: Cluster.Strategy.ErlangHosts, |
| config: [timeout: 30_000] |
| ] |
| ], |
| [name: Plausible.ClusterSupervisor] |
| ]} |
| ) |
|
|
| children = |
| [ |
| cluster, |
| Plausible.InternalStatsApiVersion, |
| {PartitionSupervisor, |
| child_spec: Task.Supervisor, name: Plausible.UserAgentParseTaskSupervisor}, |
| Plausible.Session.BalancerSupervisor, |
| Plausible.PromEx, |
| {Plausible.Auth.TOTP.Vault, key: totp_vault_key()}, |
| {Plausible.Auth.TOTP.FallbackVault, key: totp_fallback_vault_key()}, |
| Plausible.Repo, |
| Plausible.ClickhouseRepo, |
| Plausible.IngestRepo, |
| Plausible.AsyncInsertRepo, |
| Plausible.ImportDeletionRepo, |
| Plausible.Cache.Adapter.child_spec(:customer_currency, :cache_customer_currency, |
| ttl_check_interval: :timer.minutes(5), |
| n_lock_partitions: 1, |
| global_ttl: :timer.minutes(60) |
| ), |
| Plausible.Cache.Adapter.child_spec(:user_agents, :cache_user_agents, |
| ttl_check_interval: :timer.minutes(5), |
| global_ttl: :timer.minutes(60), |
| n_lock_partitions: 1, |
| ets_options: [read_concurrency: true, write_concurrency: true] |
| ), |
| Plausible.Cache.Adapter.child_specs(:sessions, :cache_sessions, |
| ttl_check_interval: :timer.seconds(10), |
| global_ttl: :timer.minutes(30), |
| n_lock_partitions: 1, |
| ets_options: [read_concurrency: true, write_concurrency: true] |
| ), |
| {Plausible.Session.Transfer, |
| base_path: Application.get_env(:plausible, :session_transfer_dir)}, |
| warmed_cache(Plausible.Site.Cache, |
| adapter_opts: [ |
| n_lock_partitions: 1, |
| ttl_check_interval: false, |
| ets_options: [read_concurrency: true] |
| ], |
| warmers: [ |
| refresh_all: |
| {Plausible.Site.Cache.All, |
| interval: :timer.minutes(15) + Enum.random(1..:timer.seconds(10))}, |
| refresh_updated_recently: |
| {Plausible.Site.Cache.RecentlyUpdated, interval: :timer.seconds(30)} |
| ] |
| ), |
| on_ee do |
| warmed_cache(Plausible.ConsolidatedView.Cache, |
| adapter_opts: [ |
| n_lock_partitions: 1, |
| ttl_check_interval: false, |
| ets_options: [read_concurrency: true] |
| ], |
| warmers: [ |
| refresh_all: |
| {Plausible.ConsolidatedView.Cache.All, |
| interval: :timer.minutes(20) + Enum.random(1..:timer.seconds(10))}, |
| refresh_updated_recently: |
| {Plausible.ConsolidatedView.Cache.RecentlyUpdated, interval: :timer.minutes(1)} |
| ] |
| ) |
| end, |
| warmed_cache(Plausible.Shield.IPRuleCache, |
| adapter_opts: [ |
| n_lock_partitions: 1, |
| ttl_check_interval: false, |
| ets_options: [read_concurrency: true] |
| ], |
| warmers: [ |
| refresh_all: |
| {Plausible.Shield.IPRuleCache.All, |
| interval: :timer.minutes(3) + Enum.random(1..:timer.seconds(10))}, |
| refresh_updated_recently: |
| {Plausible.Shield.IPRuleCache.RecentlyUpdated, interval: :timer.seconds(35)} |
| ] |
| ), |
| warmed_cache(Plausible.Shield.CountryRuleCache, |
| adapter_opts: [ |
| n_lock_partitions: 1, |
| ttl_check_interval: false, |
| ets_options: [read_concurrency: true] |
| ], |
| warmers: [ |
| refresh_all: |
| {Plausible.Shield.CountryRuleCache.All, |
| interval: :timer.minutes(3) + Enum.random(1..:timer.seconds(10))}, |
| refresh_updated_recently: |
| {Plausible.Shield.CountryRuleCache.RecentlyUpdated, interval: :timer.seconds(35)} |
| ] |
| ), |
| warmed_cache(Plausible.Shield.PageRuleCache, |
| adapter_opts: [ |
| n_lock_partitions: 1, |
| ttl_check_interval: false, |
| ets_options: [:bag, read_concurrency: true] |
| ], |
| warmers: [ |
| refresh_all: |
| {Plausible.Shield.PageRuleCache.All, |
| interval: :timer.minutes(3) + Enum.random(1..:timer.seconds(10))}, |
| refresh_updated_recently: |
| {Plausible.Shield.PageRuleCache.RecentlyUpdated, interval: :timer.seconds(35)} |
| ] |
| ), |
| warmed_cache(Plausible.Shield.HostnameRuleCache, |
| adapter_opts: [ |
| n_lock_partitions: 1, |
| ttl_check_interval: false, |
| ets_options: [:bag, read_concurrency: true] |
| ], |
| warmers: [ |
| refresh_all: |
| {Plausible.Shield.HostnameRuleCache.All, |
| interval: :timer.minutes(3) + Enum.random(1..:timer.seconds(10))}, |
| refresh_updated_recently: |
| {Plausible.Shield.HostnameRuleCache.RecentlyUpdated, interval: :timer.seconds(25)} |
| ] |
| ), |
| on_ee do |
| warmed_cache(Plausible.Stats.SamplingCache, |
| adapter_opts: [ |
| n_lock_partitions: 1, |
| ttl_check_interval: false, |
| ets_options: [read_concurrency: true] |
| ], |
| warmers: [ |
| refresh_all: |
| {Plausible.Stats.SamplingCache.All, |
| interval: :timer.hours(24) + Enum.random(1..:timer.minutes(60))} |
| ] |
| ) |
| end, |
| on_ee do |
| warmed_cache(Plausible.Site.TrackerScriptIdCache, |
| adapter_opts: [ |
| n_lock_partitions: 1, |
| ttl_check_interval: false, |
| ets_options: [read_concurrency: true] |
| ], |
| warmers: [ |
| refresh_all: |
| {Plausible.Site.TrackerScriptIdCache.All, |
| interval: :timer.minutes(180) + Enum.random(1..:timer.seconds(10))}, |
| refresh_updated_recently: |
| {Plausible.Site.TrackerScriptIdCache.RecentlyUpdated, |
| interval: :timer.seconds(30)} |
| ] |
| ) |
| else |
| warmed_cache(Plausible.Site.TrackerScriptCache, |
| adapter_opts: [ |
| n_lock_partitions: 1, |
| ttl_check_interval: false, |
| ets_options: [read_concurrency: true] |
| ], |
| warmers: [ |
| refresh_all: |
| {Plausible.Site.TrackerScriptCache.All, |
| interval: :timer.minutes(180) + Enum.random(1..:timer.seconds(10))}, |
| refresh_updated_recently: |
| {Plausible.Site.TrackerScriptCache.RecentlyUpdated, interval: :timer.seconds(30)} |
| ] |
| ) |
| end, |
| Plausible.Ingestion.Counters, |
| Plausible.Session.Salts, |
| Supervisor.child_spec(Plausible.Event.WriteBuffer, id: Plausible.Event.WriteBuffer), |
| Supervisor.child_spec(Plausible.Session.WriteBuffer, id: Plausible.Session.WriteBuffer), |
| ReferrerBlocklist, |
| {Plausible.RateLimit, clean_period: :timer.minutes(10)}, |
| {Finch, name: Plausible.Finch, pools: finch_pool_config()}, |
| {Phoenix.PubSub, name: Plausible.PubSub}, |
| endpoint, |
| {Oban, Application.get_env(:plausible, Oban)}, |
| on_ee do |
| help_scout_vault() |
| end |
| ] |
| |> List.flatten() |
| |> Enum.reject(&is_nil/1) |
|
|
| opts = [strategy: :one_for_one, name: Plausible.Supervisor] |
|
|
| setup_request_logging() |
| setup_sentry() |
| setup_opentelemetry() |
| Plausible.Ingestion.Persistor.TelemetryHandler.install() |
|
|
| setup_geolocation() |
| Location.load_all() |
| Plausible.Geo.await_loader() |
|
|
| Supervisor.start_link(List.flatten(children), opts) |
| end |
|
|
| def config_change(changed, _new, removed) do |
| PlausibleWeb.Endpoint.config_change(changed, removed) |
| :ok |
| end |
|
|
| on_ee do |
| defp help_scout_vault() do |
| help_scout_vault_key = |
| :plausible |
| |> Application.fetch_env!(Plausible.HelpScout) |
| |> Keyword.fetch!(:vault_key) |
| |> Base.decode64!() |
|
|
| [{Plausible.HelpScout.Vault, key: help_scout_vault_key}] |
| end |
| end |
|
|
| defp totp_vault_key() do |
| :plausible |
| |> Application.fetch_env!(Plausible.Auth.TOTP) |
| |> Keyword.fetch!(:vault_key) |
| end |
|
|
| defp totp_fallback_vault_key() do |
| :plausible |
| |> Application.fetch_env!(Plausible.Auth.TOTP) |
| |> Keyword.fetch!(:fallback_vault_key) |
| end |
|
|
| defp finch_pool_config() do |
| default = Application.get_env(:plausible, Plausible.Finch) |
|
|
| base_config = |
| if default do |
| %{default: default} |
| else |
| %{} |
| end |
|
|
| default_opts = default || [] |
|
|
| base_config |
| |> Map.put( |
| "https://icons.duckduckgo.com", |
| Config.Reader.merge(default_opts, conn_opts: [transport_opts: [timeout: 15_000]]) |
| ) |
| |> maybe_add_persistor_pool(default_opts) |
| |> maybe_add_sentry_pool(default_opts) |
| |> maybe_add_paddle_pool(default_opts) |
| |> maybe_add_google_pools(default_opts) |
| end |
|
|
| defp maybe_add_sentry_pool(pool_config, default) do |
| case Sentry.Config.dsn() do |
| %{endpoint_uri: "http" <> _rest = url} -> |
| Map.put(pool_config, url, Config.Reader.merge(default, size: 50)) |
|
|
| nil -> |
| pool_config |
| end |
| end |
|
|
| defp maybe_add_persistor_pool(pool_config, default) do |
| backend = |
| :plausible |
| |> Application.fetch_env!(Plausible.Ingestion.Persistor) |
| |> Keyword.fetch!(:backend) |
|
|
| persistor_conf = Application.get_env(:plausible, Plausible.Ingestion.Persistor.Remote) |
|
|
| if backend in [ |
| Plausible.Ingestion.Persistor.Remote, |
| Plausible.Ingestion.Persistor.EmbeddedWithRelay |
| ] do |
| persistor_url = Keyword.fetch!(persistor_conf, :url) |
| count = Keyword.fetch!(persistor_conf, :count) |
| timeout_ms = Keyword.fetch!(persistor_conf, :timeout_ms) |
|
|
| Map.put( |
| pool_config, |
| persistor_url, |
| Config.Reader.merge( |
| default, |
| protocols: [:http2], |
| count: count, |
| conn_opts: [transport_opts: [timeout: timeout_ms, nodelay: false]] |
| ) |
| ) |
| else |
| pool_config |
| end |
| end |
|
|
| defp maybe_add_paddle_pool(pool_config, default) do |
| paddle_conf = Application.get_env(:plausible, :paddle) |
|
|
| if paddle_conf[:vendor_id] && paddle_conf[:vendor_auth_code] do |
| Map.put( |
| pool_config, |
| Plausible.Billing.PaddleApi.vendors_domain(), |
| Config.Reader.merge(default, conn_opts: [transport_opts: [timeout: 15_000]]) |
| ) |
| else |
| pool_config |
| end |
| end |
|
|
| defp maybe_add_google_pools(pool_config, default) do |
| google_conf = Application.get_env(:plausible, :google) |
|
|
| if google_conf[:client_id] && google_conf[:client_secret] do |
| pool_config |
| |> Map.put( |
| google_conf[:api_url], |
| Config.Reader.merge(default, conn_opts: [transport_opts: [timeout: 15_000]]) |
| ) |
| |> Map.put( |
| google_conf[:reporting_api_url], |
| Config.Reader.merge(default, conn_opts: [transport_opts: [timeout: 15_000]]) |
| ) |
| else |
| pool_config |
| end |
| end |
|
|
| def setup_request_logging() do |
| :telemetry.attach( |
| "plausible-request-logging", |
| [:phoenix, :endpoint, :stop], |
| &Plausible.RequestLogger.log_request/4, |
| %{} |
| ) |
| end |
|
|
| def setup_sentry() do |
| LoggerBackends.add(Sentry.LoggerBackend) |
|
|
| :telemetry.attach_many( |
| "oban-errors", |
| [[:oban, :job, :exception], [:oban, :notifier, :exception], [:oban, :plugin, :exception]], |
| &ObanErrorReporter.handle_event/4, |
| %{} |
| ) |
| end |
|
|
| defp setup_opentelemetry() do |
| :opentelemetry_cowboy.setup() |
| OpentelemetryPhoenix.setup(adapter: :cowboy2) |
| OpentelemetryEcto.setup([:plausible, :repo], db_statement: :enabled) |
| OpentelemetryEcto.setup([:plausible, :clickhouse_repo], db_statement: :enabled) |
| OpentelemetryOban.setup() |
| Plausible.OpenTelemetry.Logger.setup() |
|
|
| if Application.get_env(:opentelemetry_experimental, :readers, []) != [] do |
| Plausible.OpenTelemetry.BeamMetrics.setup() |
| end |
| end |
|
|
| defp setup_geolocation do |
| opts = Application.fetch_env!(:plausible, Plausible.Geo) |
| :ok = Plausible.Geo.load_db(opts) |
| end |
|
|
| defp warmed_cache(impl_mod, opts) when is_atom(impl_mod) and is_list(opts) do |
| warmers = Keyword.fetch!(opts, :warmers) |
|
|
| warmer_specs = |
| Enum.map(warmers, fn {warmer_fn, {warmer_id, warmer_opts}} -> |
| {Plausible.Cache.Warmer, |
| Keyword.merge( |
| [ |
| child_name: warmer_id, |
| cache_impl: impl_mod, |
| warmer_fn: warmer_fn |
| ], |
| warmer_opts |
| )} |
| end) |
|
|
| [{impl_mod, Keyword.fetch!(opts, :adapter_opts)} | warmer_specs] |
| end |
|
|
| on_ce do |
| defp maybe_https_endpoint do |
| endpoint_config = Application.fetch_env!(:plausible, PlausibleWeb.Endpoint) |
| selfhost_config = Application.fetch_env!(:plausible, :selfhost) |
| site_encrypt_config = Keyword.get(selfhost_config, :site_encrypt) |
|
|
| if get_in(endpoint_config, [:https, :port]) do |
| PlausibleWeb.Endpoint.force_https() |
| end |
|
|
| if site_encrypt_config do |
| PlausibleWeb.Endpoint.allow_acme_challenges() |
| {SiteEncrypt.Phoenix.Endpoint, endpoint: PlausibleWeb.Endpoint} |
| else |
| PlausibleWeb.Endpoint |
| end |
| end |
| end |
| end |
|
|