defmodule Microwaveprop.Application do # See https://hexdocs.pm/elixir/Application.html # for more information on OTP Applications @moduledoc false use Application @build_timestamp DateTime.utc_now() @impl true def start(_type, _args) do topologies = Application.get_env(:libcluster, :topologies, []) children = [ MicrowavepropWeb.Telemetry, Microwaveprop.PromEx, Microwaveprop.Repo, {Cluster.Supervisor, [topologies, [name: Microwaveprop.ClusterSupervisor]]}, {Phoenix.PubSub, name: Microwaveprop.PubSub}, Microwaveprop.Cache, Microwaveprop.Propagation.ScoreCache, Microwaveprop.Weather.GridCache, {Microwaveprop.Weather.IemRateLimiter, interval_ms: Application.get_env(:microwaveprop, :iem_rate_limiter_interval_ms, 700)}, Microwaveprop.Weather.MrmsCache, Microwaveprop.Weather.NexradCache, Microwaveprop.Qrz.Client, {Oban, Application.fetch_env!(:microwaveprop, Oban)}, Microwaveprop.RepoListener, # Start to serve requests, typically the last entry MicrowavepropWeb.Endpoint, Microwaveprop.Propagation.FreshnessMonitor ] # See https://hexdocs.pm/elixir/Supervisor.html # for other strategies and supported options opts = [strategy: :one_for_one, name: Microwaveprop.Supervisor] result = Supervisor.start_link(children, opts) # Run migrations after Repo is started Microwaveprop.Release.migrate() # Warm GridCache from the latest persisted ProfilesFile so /weather # has data immediately after a pod restart. Runs after the GridCache # GenServer is up (it's in `children` above). Microwaveprop.Weather.warm_grid_cache_from_latest_profile() # Load ML model in dev/test only (Nx/Axon not compiled for prod) if Application.get_env(:microwaveprop, :load_ml_model, false) do Microwaveprop.Propagation.load_ml_model() end result end @impl true def config_change(changed, _new, removed) do MicrowavepropWeb.Endpoint.config_change(changed, removed) :ok end @doc """ Returns the deployment (image build) timestamp. Sources, in priority order: 1. The `/app/BUILD_TIMESTAMP` file baked into the Docker image at build time (the CI pipeline passes `--build-arg BUILD_TIMESTAMP=...` in the final stage, so this value is fresh on every image and is not affected by earlier-stage layer caching). The path is overridable via `config :microwaveprop, :build_timestamp_file` for tests. 2. The `DEPLOY_TIMESTAMP` environment variable, for environments that set it imperatively (e.g. `kubectl set env`). 3. The compile-time fallback captured when this module was compiled. All accepted values are ISO 8601, e.g. `2026-04-17T18:30:00Z`. """ @spec build_timestamp() :: DateTime.t() def build_timestamp do with :error <- from_file(), :error <- from_env() do @build_timestamp end end defp from_file do path = Application.get_env(:microwaveprop, :build_timestamp_file, "/app/BUILD_TIMESTAMP") case File.read(path) do {:ok, contents} -> parse_iso8601(contents) {:error, _} -> :error end end defp from_env do case System.get_env("DEPLOY_TIMESTAMP") do nil -> :error string -> parse_iso8601(string) end end defp parse_iso8601(string) do case string |> String.trim() |> DateTime.from_iso8601() do {:ok, dt, _offset} -> dt {:error, _} -> :error end end end