defmodule Mix.Tasks.ImportMibs do @shortdoc "Import MIB files from LibreNMS to priv/mibs" @moduledoc """ Imports MIB files from a LibreNMS installation to priv/mibs for inclusion in the Docker image. ## Usage # Import all MIB files mix import_mibs --source-path ~/dev/librenms/mibs # Import specific vendors only mix import_mibs --source-path ~/dev/librenms/mibs --vendors mikrotik,cisco,ubiquiti ## Options * `--source-path` - Path to the LibreNMS mibs directory (required) * `--vendors` - Comma-separated list of vendor directories to import (default: all) * `--clean` - Remove existing MIBs in priv/mibs before importing (default: false) ## Notes * Copies MIB files to priv/mibs/ which will be included in the Docker image * Preserves directory structure from LibreNMS * Standard MIB files are copied to priv/mibs/ root * Vendor-specific MIBs are copied to priv/mibs/vendor_name/ * After importing, commit the changes to git so MIBs are included in the container """ use Mix.Task require Logger def run(args) do {opts, _} = OptionParser.parse!(args, strict: [ source_path: :string, vendors: :string, clean: :boolean ] ) source_path = opts[:source_path] || raise "Missing --source-path argument" vendors = parse_vendors(opts[:vendors]) clean = opts[:clean] || false if !File.exists?(source_path) do raise "Source directory not found: #{source_path}" end dest_path = Path.join(["priv", "mibs"]) if clean do Logger.info("Cleaning priv/mibs...") _ = File.rm_rf!(dest_path) File.mkdir_p!(dest_path) else File.mkdir_p!(dest_path) end case vendors do :all -> import_all_vendors(source_path, dest_path) vendor_list -> import_specific_vendors(source_path, dest_path, vendor_list) end end defp parse_vendors(nil), do: :all defp parse_vendors(""), do: :all defp parse_vendors(vendors_string) do vendors_string |> String.split(",") |> Enum.map(&String.trim/1) |> Enum.reject(&(&1 == "")) end defp import_all_vendors(source_path, dest_path) do Logger.info("Discovering entries in #{source_path}...") entries = source_path |> File.ls!() |> Enum.reject(fn name -> name == "lost+found" or String.starts_with?(name, ".") end) |> Enum.sort() Logger.info("Found #{length(entries)} entries to import") # Separate files and directories {files, directories} = Enum.split_with(entries, fn name -> path = Path.join(source_path, name) File.regular?(path) end) # Copy top-level MIB files (standard MIBs) Logger.info("Copying #{length(files)} standard MIB files...") Enum.each(files, fn file -> copy_file(Path.join(source_path, file), Path.join(dest_path, file)) end) # Copy vendor directories Logger.info("Copying #{length(directories)} vendor directories...") Enum.each(directories, fn vendor -> copy_vendor_directory(source_path, dest_path, vendor) end) Logger.info("Import complete!") Logger.info("Remember to commit the changes: git add priv/mibs && git commit") end defp import_specific_vendors(source_path, dest_path, vendors) do Logger.info("Importing #{length(vendors)} specified vendors...") Enum.each(vendors, fn vendor -> vendor_source = Path.join(source_path, vendor) if File.exists?(vendor_source) do copy_vendor_directory(source_path, dest_path, vendor) else Logger.warning("Vendor directory not found: #{vendor}") end end) Logger.info("Import complete!") Logger.info("Remember to commit the changes: git add priv/mibs && git commit") end defp copy_vendor_directory(source_path, dest_path, vendor) do vendor_source = Path.join(source_path, vendor) vendor_dest = Path.join(dest_path, vendor) Logger.info("Copying #{vendor}...") case File.cp_r(vendor_source, vendor_dest) do {:ok, _files} -> :ok {:error, reason, file} -> Logger.error("Failed to copy #{vendor}: #{inspect(reason)} (file: #{file})") end end defp copy_file(source, dest) do case File.copy(source, dest) do {:ok, _bytes} -> :ok {:error, reason} -> Logger.error("Failed to copy #{Path.basename(source)}: #{inspect(reason)}") end end end