defmodule Mix.Tasks.ExportProfiles do @moduledoc """ Exports device SNMP profiles to JSON format for uploading to production. ## Usage # Export all profiles to JSON mix export_profiles --librenms-path ~/dev/librenms --output profiles.json # Export specific profiles mix export_profiles --librenms-path ~/dev/librenms --profiles epmp,airos-af --output profiles.json ## Options --librenms-path PATH - Path to external installation (required) --output PATH - Output JSON file path (default: profiles.json) --profiles LIST - Export only specific profiles (comma-separated, optional) ## Output Format The exported JSON file contains all profile data in a format that can be uploaded to the production server via API: POST /api/v1/admin/profiles/import Content-Type: application/json Authorization: Bearer YOUR_TOKEN """ use Mix.Task @requirements ["app.start"] @impl Mix.Task def run(args) do {opts, _, _} = OptionParser.parse(args, strict: [ librenms_path: :string, output: :string, profiles: :string ] ) librenms_path = opts[:librenms_path] || guess_librenms_path() output_file = opts[:output] || "profiles.json" profile_filter = parse_profile_filter(opts[:profiles]) if librenms_path do Mix.shell().info("Exporting profiles from: #{librenms_path}") export_profiles(librenms_path, output_file, profile_filter) else Mix.shell().error("Please specify --librenms-path option") print_usage() end end defp export_profiles(librenms_path, output_file, profile_filter) do detection_dir = Path.join(librenms_path, "resources/definitions/os_detection") discovery_dir = Path.join(librenms_path, "resources/definitions/os_discovery") detection_files = Path.wildcard(Path.join(detection_dir, "*.yaml")) # Filter profiles if specified detection_files = if profile_filter do Enum.filter(detection_files, fn file -> os_name = Path.basename(file, ".yaml") os_name in profile_filter end) else detection_files end Mix.shell().info("Found #{length(detection_files)} profiles to export") Mix.shell().info("Reading YAML files...") # Read and parse all profiles profiles = detection_files |> Enum.map(fn detection_file -> os_name = Path.basename(detection_file, ".yaml") discovery_file = Path.join(discovery_dir, "#{os_name}.yaml") Mix.shell().info(" Reading: #{os_name}") detection_data = read_yaml_file(detection_file) discovery_data = if File.exists?(discovery_file) do read_yaml_file(discovery_file) end %{ os: os_name, detection: detection_data, discovery: discovery_data } end) |> Enum.reject(&is_nil(&1.detection)) Mix.shell().info("\nWriting to: #{output_file}") # Write JSON file json_data = Jason.encode!(profiles, pretty: true) File.write!(output_file, json_data) file_size = format_bytes(File.stat!(output_file).size) Mix.shell().info("✓ Exported #{length(profiles)} profiles (#{file_size})") Mix.shell().info("\nUpload to production (requires superuser API token):") Mix.shell().info(""" curl -X POST https://towerops.net/api/v1/profiles/import \\ -H "Authorization: Bearer YOUR_SUPERUSER_API_TOKEN" \\ -H "Content-Type: application/json" \\ -d @#{output_file} Note: You must create an API token using a superuser account. Only tokens created by superusers can import device profiles. """) end defp read_yaml_file(file_path) do case YamlElixir.read_from_file(file_path) do {:ok, data} -> data {:error, reason} -> Mix.shell().error(" Failed to read #{file_path}: #{inspect(reason)}") nil end end defp parse_profile_filter(nil), do: nil defp parse_profile_filter(profiles_string) do profiles_string |> String.split(",", trim: true) |> Enum.map(&String.trim/1) 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 format_bytes(bytes) when bytes < 1024, do: "#{bytes} B" defp format_bytes(bytes) when bytes < 1024 * 1024, do: "#{Float.round(bytes / 1024, 1)} KB" defp format_bytes(bytes), do: "#{Float.round(bytes / (1024 * 1024), 1)} MB" defp print_usage do Mix.shell().info(""" Usage: mix export_profiles [options] Options: --librenms-path PATH Path to external installation (required) --output PATH Output JSON file (default: profiles.json) --profiles LIST Export specific profiles (comma-separated) Examples: # Export all profiles mix export_profiles --librenms-path ~/dev/librenms --output profiles.json # Export specific profiles mix export_profiles --librenms-path ~/dev/librenms --profiles epmp,unifi,airos-af """) end end