Implement dynamic device profile system using LibreNMS YAML definitions: - Create 7 database tables for device profiles, detection rules, sensors, processors, memory pools - Add DeviceProfiles context module with profile matching logic - Add YAML importer to parse LibreNMS os_detection and os_discovery files - Add mix task for importing profiles from LibreNMS repository - Create Dynamic profile module to interpret database definitions at runtime - Update discovery.ex to check database profiles before hard-coded modules - Fix Redis PubSub configuration to support unnamed nodes for Mix tasks Imported 5 Ubiquiti/Cambium profiles: epmp, airos-af, airos-af-ltu, airos-af60, unifi The system now supports 671+ device profiles from LibreNMS without requiring code changes for each device type.
142 lines
4.2 KiB
Elixir
142 lines
4.2 KiB
Elixir
defmodule Towerops.Application do
|
|
@moduledoc """
|
|
OTP Application module for Towerops.
|
|
|
|
Supervises the Repo, PubSub, Telemetry, Endpoint, and monitoring workers.
|
|
"""
|
|
|
|
use Application
|
|
|
|
# Capture the build timestamp at compile time (fallback for development)
|
|
@build_timestamp DateTime.utc_now()
|
|
|
|
@impl true
|
|
def start(_type, _args) do
|
|
# Run migrations on startup (Ecto handles locking for concurrent runs)
|
|
_ =
|
|
if Application.get_env(:towerops, Towerops.Repo)[:database] != "towerops_test" do
|
|
Towerops.Release.migrate()
|
|
end
|
|
|
|
topologies = Application.get_env(:libcluster, :topologies, [])
|
|
|
|
children =
|
|
[
|
|
{Cluster.Supervisor, [topologies, [name: Towerops.ClusterSupervisor]]},
|
|
ToweropsWeb.Telemetry,
|
|
Towerops.Repo,
|
|
{DNSCluster, query: Application.get_env(:towerops, :dns_cluster_query) || :ignore},
|
|
pubsub_spec(),
|
|
# Start event logger (subscribes to PubSub)
|
|
Towerops.Devices.EventLogger,
|
|
# Start monitoring supervisor
|
|
Towerops.Monitoring.Supervisor
|
|
# Start a worker by calling: Towerops.Worker.start_link(arg)
|
|
# {Towerops.Worker, arg},
|
|
] ++
|
|
exq_workers() ++
|
|
[
|
|
# Start to serve requests, typically the last entry
|
|
ToweropsWeb.Endpoint
|
|
]
|
|
|
|
# See https://hexdocs.pm/elixir/Supervisor.html
|
|
# for other strategies and supported options
|
|
opts = [strategy: :one_for_one, name: Towerops.Supervisor]
|
|
Supervisor.start_link(children, opts)
|
|
end
|
|
|
|
# Tell Phoenix to update the endpoint configuration
|
|
# whenever the application is updated.
|
|
@impl true
|
|
def config_change(changed, _new, removed) do
|
|
ToweropsWeb.Endpoint.config_change(changed, removed)
|
|
:ok
|
|
end
|
|
|
|
# Configure Exq workers - don't start in test environment
|
|
defp exq_workers do
|
|
if Application.get_env(:towerops, :env) == :test do
|
|
[]
|
|
else
|
|
[
|
|
{Exq, exq_config()}
|
|
]
|
|
end
|
|
end
|
|
|
|
defp exq_config do
|
|
redis_config = Application.get_env(:towerops, :redis, [])
|
|
|
|
[
|
|
name: Exq,
|
|
host: Keyword.get(redis_config, :host, "localhost"),
|
|
port: Keyword.get(redis_config, :port, 6379),
|
|
namespace: "exq",
|
|
concurrency: 10,
|
|
queues: ["default", "discovery", "polling", "monitoring", "maintenance"]
|
|
]
|
|
end
|
|
|
|
# Returns PubSub spec - uses Redis in production/clustered environments,
|
|
# falls back to default PG2 adapter for Mix tasks and development
|
|
defp pubsub_spec do
|
|
redis_config = Application.get_env(:towerops, :redis, [])
|
|
node_name = node()
|
|
|
|
# Use Redis adapter only when:
|
|
# 1. Redis config exists with host
|
|
# 2. Node has a name (not nonode@nohost)
|
|
use_redis? =
|
|
Keyword.has_key?(redis_config, :host) &&
|
|
node_name != :nonode@nohost
|
|
|
|
if use_redis? do
|
|
{Phoenix.PubSub, name: Towerops.PubSub, adapter: Phoenix.PubSub.Redis, redis_config: redis_pubsub_config()}
|
|
else
|
|
# Default PG2 adapter for Mix tasks and development
|
|
{Phoenix.PubSub, name: Towerops.PubSub}
|
|
end
|
|
end
|
|
|
|
defp redis_pubsub_config do
|
|
redis_config = Application.get_env(:towerops, :redis, [])
|
|
|
|
[
|
|
host: Keyword.get(redis_config, :host, "localhost"),
|
|
port: Keyword.get(redis_config, :port, 6379),
|
|
node_name: node()
|
|
]
|
|
end
|
|
|
|
@doc """
|
|
Returns the deployment timestamp.
|
|
|
|
In production (Kubernetes), this reads from the DEPLOY_TIMESTAMP environment
|
|
variable which is automatically set by GitLab CI during deployment. Falls back
|
|
to compile time in development.
|
|
|
|
The timestamp is set by GitLab CI using:
|
|
```bash
|
|
kubectl set env deployment/towerops DEPLOY_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") -n towerops
|
|
```
|
|
|
|
This ensures all pods in the deployment show the same deployment time, regardless
|
|
of when individual pods were created or restarted.
|
|
"""
|
|
@spec build_timestamp() :: DateTime.t()
|
|
def build_timestamp do
|
|
case System.get_env("DEPLOY_TIMESTAMP") do
|
|
nil ->
|
|
# Development/fallback: use compile time
|
|
@build_timestamp
|
|
|
|
timestamp_string ->
|
|
# Production: parse from environment variable
|
|
case DateTime.from_iso8601(timestamp_string) do
|
|
{:ok, datetime, _offset} -> datetime
|
|
{:error, _} -> @build_timestamp
|
|
end
|
|
end
|
|
end
|
|
end
|