diff --git a/config/dev.exs b/config/dev.exs index b1afcfa7..7d587a49 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -3,6 +3,15 @@ import Config # Do not include metadata nor timestamps in development logs config :logger, :default_formatter, format: "[$level] $message\n" +# Read-only secondary repo pointing at the local aprs.me dev database. +# See lib/microwaveprop/aprs_repo.ex — used by APRS calibration tooling +# only; the main app boots fine without it. +config :microwaveprop, Microwaveprop.AprsRepo, + username: "postgres", + hostname: "localhost", + database: "aprsme_dev", + pool_size: 2 + # Configure your database config :microwaveprop, Microwaveprop.Repo, username: "postgres", diff --git a/config/runtime.exs b/config/runtime.exs index c22a1833..5a0a61dd 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -1,5 +1,10 @@ import Config +alias Microwaveprop.Workers.CanadianSoundingFetchWorker +alias Oban.Plugins.Cron + +require Logger + # config/runtime.exs is executed for all environments, including # during releases. It is executed after compilation and before the # system starts, so it is typically used to load production configuration @@ -16,9 +21,6 @@ import Config # # Alternatively, you can use `mix phx.gen.release` to generate a `bin/server` # script that automatically sets the env var above. -alias Microwaveprop.Workers.CanadianSoundingFetchWorker -alias Oban.Plugins.Cron - if System.get_env("PHX_SERVER") do config :microwaveprop, MicrowavepropWeb.Endpoint, server: true end @@ -355,6 +357,24 @@ if config_env() == :prod do # pool_count: 4, socket_options: maybe_ipv6 + # Optional secondary repo for read-only access to aprs.me's database. + # Used by APRS-based 144 MHz calibration tooling. When the env var is + # unset we leave the AprsRepo unconfigured; Microwaveprop.Application + # logs a warning and skips starting it, and the rest of the app boots + # normally. + case System.get_env("APRS_DATABASE_URL") do + nil -> + Logger.warning("APRS_DATABASE_URL not set — Microwaveprop.AprsRepo will not start; APRS calibration disabled.") + + :ok + + aprs_url -> + config :microwaveprop, Microwaveprop.AprsRepo, + url: aprs_url, + pool_size: String.to_integer(System.get_env("APRS_POOL_SIZE") || "2"), + socket_options: maybe_ipv6 + end + config :microwaveprop, MicrowavepropWeb.Endpoint, # SSL terminated by Cloudflare tunnel; generated URLs still use https url: [host: host, port: 443, scheme: "https"], diff --git a/config/test.exs b/config/test.exs index fe101cf5..854b94a4 100644 --- a/config/test.exs +++ b/config/test.exs @@ -1,5 +1,6 @@ import Config +alias Ecto.Adapters.SQL.Sandbox alias Microwaveprop.Qrz.Client # Only in tests, remove the complexity from the password hashing algorithm @@ -15,6 +16,21 @@ config :microwaveprop, Client, plug: {Req.Test, Client}, retry: false +# Read-only secondary repo for aprs.me. Configured here so the app boots +# under MIX_ENV=test, but uses the SQL.Sandbox in :manual mode so tests +# that don't touch APRS data don't have to check the DB out. Tests that +# do query the AprsRepo must call `Ecto.Adapters.SQL.Sandbox.checkout/1` +# explicitly. +config :microwaveprop, Microwaveprop.AprsRepo, + username: "postgres", + password: "postgres", + hostname: "localhost", + database: "aprsme_test#{System.get_env("MIX_TEST_PARTITION")}", + pool: Sandbox, + pool_size: 2, + ownership_timeout: 60_000, + timeout: 60_000 + config :microwaveprop, Microwaveprop.Geocoder, plug: {Req.Test, Microwaveprop.Geocoder}, retry: false @@ -33,7 +49,7 @@ config :microwaveprop, Microwaveprop.Repo, password: "postgres", hostname: "localhost", database: "microwaveprop_test#{System.get_env("MIX_TEST_PARTITION")}", - pool: Ecto.Adapters.SQL.Sandbox, + pool: Sandbox, pool_size: System.schedulers_online() * 2, ownership_timeout: 60_000, timeout: 60_000 diff --git a/lib/microwaveprop/application.ex b/lib/microwaveprop/application.ex index 8a7a89ed..7781f48d 100644 --- a/lib/microwaveprop/application.ex +++ b/lib/microwaveprop/application.ex @@ -17,6 +17,10 @@ defmodule Microwaveprop.Application do MicrowavepropWeb.Telemetry, Microwaveprop.PromEx, Microwaveprop.Repo, + # AprsRepo is optional — only starts when configured with a :url + # (dev/test from config blocks, prod from APRS_DATABASE_URL). Missing + # config logs a warning and skips startup so the app still boots. + aprs_repo_child_spec(), {Cluster.Supervisor, [topologies, [name: Microwaveprop.ClusterSupervisor]]}, {Phoenix.PubSub, name: Microwaveprop.PubSub}, # Partitioned Task.Supervisor — callers route heavy `async_stream` @@ -43,7 +47,7 @@ defmodule Microwaveprop.Application do # 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) + result = children |> Enum.reject(&is_nil/1) |> Supervisor.start_link(opts) # Attach the Oban exception telemetry tap so per-worker recovery # callbacks and structured logs fire on job failure. Must run after @@ -92,6 +96,23 @@ defmodule Microwaveprop.Application do :ok end + # Returns Microwaveprop.AprsRepo child spec if configured (has a :url + # or :database key), otherwise logs a warning and returns nil so the + # supervisor children list can filter it out. APRS calibration is an + # optional integration with the sister aprs.me database — we never + # crash boot when it's unavailable. + defp aprs_repo_child_spec do + config = Application.get_env(:microwaveprop, Microwaveprop.AprsRepo, []) + + if Keyword.has_key?(config, :url) or Keyword.has_key?(config, :database) do + Microwaveprop.AprsRepo + else + Logger.warning("Microwaveprop.AprsRepo not configured (no :url or :database) — APRS calibration disabled.") + + nil + end + end + @doc """ Returns the deployment (image build) timestamp. diff --git a/lib/microwaveprop/aprs_repo.ex b/lib/microwaveprop/aprs_repo.ex new file mode 100644 index 00000000..14c122a5 --- /dev/null +++ b/lib/microwaveprop/aprs_repo.ex @@ -0,0 +1,17 @@ +defmodule Microwaveprop.AprsRepo do + @moduledoc """ + Secondary repo for read-only access to aprs.me's `packets` table. + + Used by `Microwaveprop.Aprs` and the `Mix.Tasks.Calibrate.Aprs144` mix + task to pull verified RF hops as ground truth for 144 MHz scoring. + + This repo never owns schema, migrations, or writes — it only ever + issues SELECT queries against aprs.me's existing partitioned `packets` + table. The connection is configured to fail closed if `APRS_DATABASE_URL` + is unset in production. + """ + use Ecto.Repo, + otp_app: :microwaveprop, + adapter: Ecto.Adapters.Postgres, + read_only: true +end