defmodule Mix.Tasks.ImportProfiles do @moduledoc """ Imports device SNMP profiles from external YAML files. ## Usage # Import a single profile mix import_profiles --profile epmp --detection ~/dev/librenms/resources/definitions/os_detection/epmp.yaml --discovery ~/dev/librenms/resources/definitions/os_discovery/epmp.yaml # Import specific profiles mix import_profiles --profiles epmp,airos-af,unifi # Import all profiles from external directory mix import_profiles --all --librenms-path ~/dev/librenms ## Options --profile NAME - Import a single profile by name --profiles LIST - Import multiple profiles (comma-separated) --all - Import all profiles from external --librenms-path PATH - Path to external installation --detection PATH - Path to detection YAML file --discovery PATH - Path to discovery YAML file (optional) --force - Delete existing profile before importing """ use Mix.Task alias Towerops.DeviceProfiles alias Towerops.DeviceProfiles.Importer alias Towerops.Repo @requirements ["app.start"] @impl Mix.Task def run(args) do {opts, _, _} = OptionParser.parse(args, strict: [ profile: :string, profiles: :string, all: :boolean, librenms_path: :string, detection: :string, discovery: :string, force: :boolean ] ) cond do opts[:all] -> import_all_profiles(opts) opts[:profiles] -> import_multiple_profiles(opts) opts[:profile] || opts[:detection] -> import_single_profile(opts) true -> print_usage() end end defp import_single_profile(opts) do detection_file = opts[:detection] discovery_file = opts[:discovery] force = opts[:force] || false if detection_file && File.exists?(detection_file) do os_name = Path.basename(detection_file, ".yaml") if force do delete_existing_profile(os_name) end Mix.shell().info("Importing profile: #{os_name}") case Importer.import_profile(detection_file, discovery_file) do {:ok, profile} -> Mix.shell().info("Successfully imported profile: #{profile.os}") print_profile_stats(profile) {:error, reason} -> Mix.shell().error("Failed to import profile: #{inspect(reason)}") end else Mix.shell().error("Detection file not found: #{detection_file}") end end defp import_multiple_profiles(opts) do profiles = String.split(opts[:profiles], ",", trim: true) librenms_path = opts[:librenms_path] || guess_librenms_path() force = opts[:force] || false if librenms_path do detection_dir = Path.join(librenms_path, "resources/definitions/os_detection") discovery_dir = Path.join(librenms_path, "resources/definitions/os_discovery") Enum.each(profiles, fn profile_name -> import_profile_by_name(profile_name, detection_dir, discovery_dir, force) end) else Mix.shell().error("external path not found. Use --librenms-path option.") end end defp import_all_profiles(opts) do librenms_path = opts[:librenms_path] || guess_librenms_path() force = opts[:force] || false if librenms_path do detection_dir = Path.join(librenms_path, "resources/definitions/os_detection") discovery_dir = Path.join(librenms_path, "resources/definitions/os_discovery") if force do Mix.shell().info("Deleting all existing profiles...") delete_all_profiles() end Mix.shell().info("Importing all profiles from: #{librenms_path}") Mix.shell().info("This may take several minutes...") {:ok, stats} = Importer.import_all_from_directory(detection_dir, discovery_dir) Mix.shell().info("\nImport complete!") Mix.shell().info("Successfully imported: #{stats.success} profiles") Mix.shell().info("Failed to import: #{stats.failed} profiles") else Mix.shell().error("external path not found. Use --librenms-path option.") end end defp import_profile_by_name(profile_name, detection_dir, discovery_dir, force) do detection_file = Path.join(detection_dir, "#{profile_name}.yaml") discovery_file = Path.join(discovery_dir, "#{profile_name}.yaml") discovery_file = if File.exists?(discovery_file), do: discovery_file if File.exists?(detection_file) do if force do delete_existing_profile(profile_name) end Mix.shell().info("Importing profile: #{profile_name}") case Importer.import_profile(detection_file, discovery_file) do {:ok, profile} -> Mix.shell().info("✓ Successfully imported: #{profile.os}") {:error, reason} -> Mix.shell().error("✗ Failed to import #{profile_name}: #{inspect(reason)}") end else Mix.shell().error("Detection file not found for: #{profile_name}") end end defp delete_existing_profile(os_name) do if profile = DeviceProfiles.get_profile(os_name) do Mix.shell().info("Deleting existing profile: #{os_name}") DeviceProfiles.delete_profile(profile) end end defp delete_all_profiles do profiles = DeviceProfiles.list_profiles() Enum.each(profiles, fn profile -> DeviceProfiles.delete_profile(profile) end) Mix.shell().info("Deleted #{length(profiles)} existing profiles") end defp guess_librenms_path do home = System.get_env("HOME") possible_paths = ["~/dev/librenms", "#{home}/dev/librenms", "/opt/librenms"] possible_paths |> Enum.find(fn path -> expanded = Path.expand(path) File.dir?(expanded) end) |> case do nil -> nil path -> Path.expand(path) end end defp print_profile_stats(profile) do profile = Repo.preload(profile, [:detection_rules, :sensor_definitions, :processor_definitions]) Mix.shell().info("") Mix.shell().info(" OS: #{profile.os}") Mix.shell().info(" Text: #{profile.text}") Mix.shell().info(" Type: #{profile.type}") Mix.shell().info(" Detection rules: #{length(profile.detection_rules)}") Mix.shell().info(" Sensor definitions: #{length(profile.sensor_definitions)}") Mix.shell().info(" Processor definitions: #{length(profile.processor_definitions)}") Mix.shell().info("") end defp print_usage do Mix.shell().info(""" Usage: mix import_profiles [options] Options: --profile NAME Import a single profile by name --profiles LIST Import multiple profiles (comma-separated) --all Import all profiles from external --librenms-path PATH Path to external installation --detection PATH Path to detection YAML file --discovery PATH Path to discovery YAML file (optional) --force Delete existing profile before importing Examples: # Import specific profiles mix import_profiles --profiles epmp,airos-af,unifi --librenms-path ~/dev/librenms # Import a single profile with explicit paths mix import_profiles --detection ~/dev/librenms/resources/definitions/os_detection/epmp.yaml --discovery ~/dev/librenms/resources/definitions/os_discovery/epmp.yaml # Import all profiles (takes several minutes) mix import_profiles --all --librenms-path ~/dev/librenms """) end end