Netbox Agent

Netbox Agent

spectroman
spectroman
6 views

Previously I used to use PHP-IPAM, but then Netbox is out there and it is also nice.

But adding all the stuff is annoying. Even more because Netbox doesn't have ascanners or any sort of helper.

So lets build one shall we?

Repo
Release

Building a netbox-agent

A multi-platform inventory agent that collects system and network data and upserts it into NetBox DCIM/IPAM. Runs as a daemon or one-shot job. Supports Linux, FreeBSD, TrueNAS, pfSense, macOS, and Windows.

Two implementations exist:

Implementation Location Status
Go (recommended) golang/ Active — full feature set
Ruby ruby/ Legacy — local-host only

Modes of operation

The Go binary is a single executable with multiple modes, selected by flag:

Flag What it does
(default) Collect local host info (hardware, OS, interfaces, IPs) and sync to NetBox as a device or VM
--cisco SSH into Cisco switches, collect show command output, sync to NetBox
--unifi SSH into UniFi APs and Cloud Keys, sync to NetBox
--scan nmap -sn a subnet and upsert all live IPs into NetBox IPAM
--metallb Run as a Kubernetes pod — discover MetalLB pools and sync to NetBox IPAM
--sysinfo Print collected local info to stdout; does not write to NetBox

All write operations support --dry-run (logs payloads, no POST/PATCH).


Quick start

# Build
cd golang && go mod download && make build

# Install (binary + config + systemd unit)
sudo make install

# Edit config
sudo nano /etc/netbox-agent/config.yml

# Start as daemon
sudo systemctl enable --now netbox-agent

# Or run once
netbox-agent --once

Configuration

/etc/netbox-agent/config.yml (default path; override with NETBOX_AGENT_CONFIG):

netbox:
  url:          "https://netbox.example.com/api/"
  token:        "Bearer nbt_XXXX.YYYYYYYYYYYYYYYYYY"
  site:         "Main DC"           # created in NetBox if absent
  cluster:      "KVM Cluster"       # VMs only
  cluster_type: "KVM"               # VMs only
  insecure_tls: false               # skip TLS verify (self-signed certs)

device:
  # physical: true                  # omit to auto-detect via systemd-detect-virt
  role: "Server"
  # manufacturer: "Raspberry Pi Foundation"   # override if detection fails
  # model: "Raspberry Pi 4 Model B 8GB"
  u_height: 1                       # rack units in NetBox device type

agent:
  interval_seconds: 3600            # re-sync every hour

# Cisco switches (--cisco mode)
cisco:
  platform: ios          # global default: ios | iosxe | nxos | sx220
  user: admin
  password: secret
  timeout_seconds: 30
  switches:
    - host: 192.168.1.1
      site: "Main DC"
      role: "Network Switch"
      cdp: true          # collect CDP neighbors
    - host: 192.168.1.2
      platform: nxos

# UniFi APs and Cloud Keys (--unifi mode)
unifi:
  user: ubnt
  password: secret
  timeout_seconds: 30
  aps:
    - host: 192.168.1.10
      site: "Main DC"
      location: "Server Room"

Config is hot-reloaded on every interval — no restart required.


What it collects

Local host (default mode)

Category Details
Identity Hostname, OS name + version
Hardware Manufacturer, model, serial, asset tag
CPU Model, core count, socket count
Memory Total RAM in MB
Disks Name and size of all physical block devices
Interfaces Name, MAC, MTU, speed, NetBox type
IP Addresses All non-link-local IPv4/IPv6 per interface, with reverse DNS
Primary IP Detected from default-route interface, wired to device/VM record

Physical vs virtual is auto-detected via systemd-detect-virt and DMI product name. Override with device.physical: true/false.

Cisco switches (--cisco)

Collected via SSH using show commands:

  • Hostname, model, serial, IOS version (via show version / show inventory)
  • All interfaces: name, type, MAC, speed, MTU, admin/line status, IP
  • VLANs: ID and name (via show vlan brief)
  • CDP neighbors and cable creation in NetBox (optional, cdp: true)

Platforms: ios, iosxe, nxos, sx220. Platform is auto-detected from show version if not set.

UniFi devices (--unifi)

Device class is auto-detected:

Class Detection Collection method
AP (UAP-, U6-, BusyBox/MIPS) Default mca-dump JSON + info command
Cloud Key (UCK-G2, UCK-G2-Plus, Debian/aarch64) uname -a contains "cloud-key" or arch=aarch64+Debian /proc/*, ip addr, lsblk, df

Pushed to NetBox:

  • Manufacturer: Ubiquiti; device type per model
  • Wired interfaces (from if_table)
  • Wireless interfaces: one per radio + one per SSID/VAP
  • Management IP set as primary IPv4
  • Cloud Key: CPU cores, storage, HDD, battery state
  • AP: radio bands/channels, SSIDs

MetalLB (--metallb)

Run as a Kubernetes pod. Discovers pools via:

  1. Kubernetes API (metallb.io/v1beta1/ipaddresspools) — primary
  2. MetalLB speaker Prometheus metrics scrape — fallback

Pushes to NetBox IPAM:

  • CIDR pools → ipam/prefixes/ (tagged metallb)
  • Range pools (e.g. 10.0.0.100-10.0.0.150) → ipam/ip-ranges/
  • LoadBalancer service IPs → ipam/ip-addresses/ (dns_name = <svc>.<ns>.svc)

Subnet scanner (--scan)

netbox-agent --scan --scan-subnet 192.168.1.0/24 \
  --netbox-url https://netbox.example.com/api/ \
  --netbox-token "Bearer nbt_XXX.YYY"
  1. Ensures prefix exists in ipam/prefixes/
  2. Runs nmap -sn <subnet> to find live hosts
  3. Reverse-DNS each host
  4. Upserts each IP into ipam/ip-addresses/

Requires nmap installed on the host.


Hardware detection

The agent tries five sources in priority order, stopping at the first that works:

Priority Source Platforms
1 dmidecode (DMI types 1/2/3) x86 Linux (requires root)
2 kenv smbios.* FreeBSD, pfSense, TrueNAS CORE
3 /proc/device-tree/model Raspberry Pi, ARM SBCs
4 /proc/cpuinfo Hardware/Revision Older RPi kernels
5 dmesg "Machine model:" / "Board:" Embedded fallback

Dell servers: system serial = service tag → stored as asset_tag; baseboard serial → stored as serial. All other vendors: system serial → serial.

Raspberry Pi: full revision table from Pi 1 Model B through Pi 5, all Compute Modules and Zero variants.

Manufacturer inference: when maker field is blank, derived from model string (Raspberry Pi Foundation, Xunlong, Radxa, Sinovoip, Hardkernel, BeagleBoard.org, Pine64).

Override in config with device.manufacturer and device.model if detection fails.


Interface filtering

Reported (kept):

  • Physical Ethernet: eth*, eno*, ens*, enp*, enx*, em*, igb*, ix*, bge*
  • WiFi: wl*, ath*
  • Bonds/LAG: bond*, lagg*
  • Bridges: vmbr*, bridge*, ovs*
  • VLANs: vlan*, *.{digits}
  • VPN tunnels: ovpn*, tun*, wg*, pppoe*

Skipped:

  • Loopback (lo, lo0)
  • Container plumbing: veth*, docker*, podman*, br-*, virbr*
  • Proxmox firewall internals: fwbr*, fwln*, fwpr*
  • pfSense/OpenBSD internals: pflog*, pfsync*, enc*
  • Kernel dummies: dummy*, ifb*, sit*, ip6tnl*
  • QEMU taps: tap*
  • Calico CNI: cali*

Upsert behaviour

Every object (device/VM, interface, IP, prefix, etc.) is looked up by natural key before writing:

  • ExistsPATCH changed fields only
  • AbsentPOST to create
  • Nothing is ever deleted

The comments field on every device/VM is kept as structured data with a Last seen: timestamp so each run is traceable without polluting audit logs.


Reverse DNS resolution

Three sources, tried in order:

  1. dig +short -x <ip> (respects configured DNS)
  2. /etc/hosts (works offline)
  3. getent hosts <ip> (covers NSS: avahi/mDNS, LDAP, etc.)

Platform support

Linux (standard)

  • Hardware: dmidecode (root) or device-tree
  • Interfaces: /sys/class/net
  • IPs: ip -o addr show
  • Routing: ip route show default
  • OS: /etc/os-release

FreeBSD / pfSense / TrueNAS CORE

  • Hardware: kenv smbios.*
  • Interfaces: ifconfig -a (hex netmask conversion)
  • Routing: route -n get default or netstat -rn
  • OS: /etc/version (pfSense), /etc/version.freenas (TrueNAS CORE), uname -sr fallback

TrueNAS SCALE (Debian 12)

Use linux-amd64 binary. Root filesystem is read-only and reset on upgrades — store binary and config in a ZFS dataset (/mnt/<pool>/). See golang/README-truenas.md for full deployment instructions.

macOS

Hardware detection falls back to "Generic" (no /proc or dmidecode). Useful for local testing.

Windows

Limited info (no /proc, dmidecode, or ip). Provided for completeness.


Building

cd golang

# Current machine
make build

# All targets (dist/)
make all-targets

# Specific targets
make linux-amd64
make linux-arm64
make linux-armv6       # RPi 1/2/Zero
make linux-freebsd     # TrueNAS CORE / pfSense
make darwin-arm64      # Apple Silicon
make windows-amd64

Cross-compilation reference:

Target Command Binary
Linux x86-64 make linux-amd64 dist/netbox-agent-linux-amd64
Linux ARM64 (RPi 3/4/5) make linux-arm64 dist/netbox-agent-linux-arm64
Linux ARMv6 (RPi 1/2/Zero) make linux-armv6 dist/netbox-agent-linux-armv6
FreeBSD / pfSense / TrueNAS CORE make linux-freebsd dist/netbox-agent-freebsd-amd64
macOS Intel make darwin-amd64 dist/netbox-agent-darwin-amd64
macOS Apple Silicon make darwin-arm64 dist/netbox-agent-darwin-arm64
Windows x86-64 make windows-amd64 dist/netbox-agent-windows-amd64.exe

Installation

Manual

cd golang
sudo make install
# Installs binary, config, and both systemd units; does not overwrite existing config.

Or manually:

sudo install -m 0755 dist/netbox-agent-linux-amd64 /usr/local/bin/netbox-agent
sudo mkdir -p /etc/netbox-agent
sudo install -m 0600 config.yml /etc/netbox-agent/config.yml
sudo install -m 0644 netbox-agent.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now netbox-agent

Systemd modes

Unit Behaviour
netbox-agent.service Long-running daemon, re-syncs on interval
netbox-agent-oneshot.service Runs once at boot, exits

Ansible

See ansible/README.md. The role downloads the correct binary from the GitLab package registry, writes the config, and installs the appropriate systemd unit.

- role: netbox_agent
  vars:
    netbox_agent_netbox_url:   "https://netbox.example.com/api/"
    netbox_agent_netbox_token: "Bearer nbt_XXXX.YYYY"
    netbox_agent_site:         "Main DC"
    netbox_agent_device_role:  "Server"
    netbox_agent_service_mode: "daemon"    # or "oneshot" or "push"
    netbox_agent_version:      "latest"

Key Ansible variables:

Variable Default Description
netbox_agent_version "latest" Tag to install; pin for reproducibility
netbox_agent_service_mode "daemon" daemon / oneshot / push
netbox_agent_physical null (auto) Force physical/virtual detection
netbox_agent_interval_seconds 3600 Sync interval (daemon mode)
netbox_agent_insecure_tls false Skip TLS verification
netbox_agent_push_cleanup true Remove binary after push-mode run

MetalLB container

cd golang
make linux-amd64
docker build -f Dockerfile.metallb -t netbox-agent-metallb .

The image is based on scratch with only the static binary and Mozilla CA bundle. Default command: --metallb --once.

Required RBAC: get/list on metallb.io/ipaddresspools and v1/services.


CLI flags reference

netbox-agent [flags]

Global:
  --config PATH        Config file (default: /etc/netbox-agent/config.yml; or NETBOX_AGENT_CONFIG env)
  --once               Run once and exit
  --dry-run            Collect but do NOT write to NetBox
  --verbose            Log full HTTP request/response bodies
  --sysinfo            Print local system info and exit
  --version            Print version and exit

NetBox overrides:
  --netbox-url URL     Override config netbox.url
  --netbox-token TOK   Override config netbox.token
  --site NAME          Override config netbox.site

Cisco (--cisco):
  --cisco-host IP      Single switch (skips config switch list)
  --cisco-user USER
  --cisco-password PW
  --cisco-platform     ios | iosxe | nxos | sx220 (auto-detected if omitted)
  --cisco-enable       Send 'enable' after login
  --cisco-cdp          Collect CDP neighbors, create cables
  --cisco-site NAME
  --cisco-role NAME    Default: "Network Switch"
  --cisco-dry-run

UniFi (--unifi):
  --unifi-host IP      Single device (AP or Cloud Key, auto-detected)
  --unifi-user USER    Default: ubnt
  --unifi-password PW
  --unifi-site NAME
  --unifi-location NAME   NetBox location (room/area)
  --unifi-dry-run

Scan (--scan):
  --scan-subnet CIDR   e.g. 192.168.1.0/24
  --scan-dry-run

Project layout

netbox-agent/
├── golang/                         # Go implementation (main)
│   ├── main.go                     # Entry point, flag parsing, run loop
│   ├── collector/
│   │   ├── sysinfo.go              # Hardware, CPU, RAM, disk, interface collectors
│   │   ├── agent.go                # NetBox upsert orchestration (device/VM)
│   │   ├── cisco.go                # Cisco SSH entry point + fact gathering
│   │   ├── cisco_ssh.go            # SSH client (prompt detection, paging)
│   │   ├── cisco_parser.go         # show command parsers
│   │   ├── cisco_push.go           # Cisco NetBox push logic
│   │   ├── unifi.go                # UniFi AP + Cloud Key collection and push
│   │   └── metallb.go              # MetalLB pool discovery and IPAM push
│   ├── netbox/
│   │   ├── client.go               # NetBox HTTP API client (GET/POST/PATCH/Find)
│   │   └── comment.go              # Structured comment helpers
│   ├── config/
│   │   └── config.go               # YAML config loader + structs
│   ├── scanner/
│   │   └── scanner.go              # nmap subnet scan + IPAM upsert
│   ├── Makefile                    # Build, cross-compile, install, upload targets
│   ├── Dockerfile.metallb          # Scratch image for MetalLB pod
│   ├── netbox-agent.service        # systemd daemon unit
│   ├── netbox-agent-oneshot.service
│   ├── config.yml                  # Example configuration
│   ├── go.mod
│   ├── README.md                   # Go implementation docs
│   └── README-truenas.md           # TrueNAS-specific deployment notes
├── ruby/                           # Legacy Ruby implementation
│   ├── netbox_agent.rb
│   ├── netbox-agent.config.yml
│   ├── netbox-agent.service
│   └── README.md
├── ansible/                        # Ansible role
│   ├── README.md
│   └── roles/netbox_agent/
│       ├── defaults/main.yml
│       ├── tasks/main.yml
│       ├── handlers/main.yml
│       └── templates/
│           ├── config.yml.j2
│           ├── netbox-agent.service.j2
│           └── netbox-agent-oneshot.service.j2
└── support-files/
    └── RELEASE.md                  # Release notes template

Requirements

Go implementation

Dependency Notes
Go 1.21+ Build-time only; binary is fully static
dmidecode Optional. Hardware info on x86. Requires root.
kenv Optional. Hardware info on FreeBSD. No root required.
dig Optional. Reverse DNS. (dnsutils / bind-utils)
ip Interface/IP enumeration. Standard (iproute2).
nmap Required for --scan mode only.
NetBox 4.x API token in Bearer nbt_... format.

The compiled binary has no runtime dependencies — copy to any Linux/BSD machine and run.

Ruby implementation

Ruby 2.7+, standard library only (no gems). Same optional system tools as above.


Monitoring the service

# Live logs
journalctl -u netbox-agent -f

# Status
systemctl status netbox-agent

# Trigger immediate re-sync
systemctl restart netbox-agent

# Stop
systemctl stop netbox-agent

netbox-agent Ansible Role

---
# example-playbook.yml
# Run with:
#   ansible-playbook -i inventory example-playbook.yml \
#     -e netbox_agent_gitlab_token=YOUR_TOKEN

- name: Deploy netbox-agent to virtual machines
  hosts: vms
  become: true

  roles:
    - role: netbox_agent
      vars:
        netbox_agent_netbox_url:    "https://netbox.science.net/api/"
        netbox_agent_netbox_token:  "Bearer nbt_XXXX.YYYYYYYYYYYYYY"
        netbox_agent_insecure_tls:  true
        netbox_agent_site:          "HeadQuarters"
        netbox_agent_cluster:       "KVM Cluster"
        netbox_agent_cluster_type:  "KVM"
        netbox_agent_device_role:   "Virtual Machine"
        netbox_agent_service_mode:  "oneshot"   # run once at boot for VMs
        netbox_agent_version:       "latest"

- name: Deploy netbox-agent to physical servers (daemon mode)
  hosts: servers
  become: true

  roles:
    - role: netbox_agent
      vars:
        netbox_agent_netbox_url:           "https://netbox.science.net/api/"
        netbox_agent_netbox_token:         "Bearer nbt_XXXX.YYYYYYYYYYYYYY"
        netbox_agent_insecure_tls:         true
        netbox_agent_site:                 "HeadQuarters"
        netbox_agent_device_role:          "Server"
        netbox_agent_physical:             true
        netbox_agent_service_mode:         "daemon"
        netbox_agent_interval_seconds:     3600
        netbox_agent_version:              "latest"

TrueNAS Deployment Notes

TrueNAS exists in two very different variants, each requiring a different binary
and deployment approach.


TrueNAS CORE (FreeBSD-based)

TrueNAS CORE runs on FreeBSD 13.x. Use the freebsd-amd64 binary.

Hardware detection

  • kenv smbios.* is used instead of dmidecode — works without root on FreeBSD.
  • Interface collection uses ifconfig -a parsing (no /sys/class/net).
  • Set device.manufacturer and device.model in config if kenv returns nothing useful.
  • Serial number is read from kenv smbios.system.serial.

Installation

TrueNAS CORE allows persistent storage in jails or in /data and /root.
Do not install to /usr/local/bin directly — it will be wiped on major upgrades.

Recommended approach — run from /data:

# Copy binary
cp netbox-agent-freebsd-amd64 /data/netbox-agent
chmod 755 /data/netbox-agent

# Create config
mkdir -p /data/netbox-agent-config
cat > /data/netbox-agent-config/config.yml <<EOF
netbox:
  url: "https://netbox.example.com/api/"
  token: "Bearer nbt_XXXX.YYYY"
  site: "Main DC"
  insecure_tls: false
device:
  physical: true
  role: "NAS"
agent:
  interval_seconds: 3600
EOF
chmod 600 /data/netbox-agent-config/config.yml

# Run once to verify
/data/netbox-agent --once --config /data/netbox-agent-config/config.yml --sysinfo

Scheduling on TrueNAS CORE

Use the built-in Tasks → Cron Jobs UI, or add to /etc/crontab:

0 * * * * root /data/netbox-agent --once --config /data/netbox-agent-config/config.yml

Or use an rc.d script for daemon mode — place in /usr/local/etc/rc.d/netbox_agent:

#!/bin/sh
# PROVIDE: netbox_agent
# REQUIRE: NETWORKING
# KEYWORD: shutdown

. /etc/rc.subr

name="netbox_agent"
rcvar="netbox_agent_enable"
command="/data/netbox-agent"
command_args="--config /data/netbox-agent-config/config.yml"
pidfile="/var/run/netbox_agent.pid"

load_rc_config $name
: ${netbox_agent_enable:="NO"}

run_rc_command "$1"

Then enable it:

echo 'netbox_agent_enable="YES"' >> /etc/rc.conf.local
service netbox_agent start

TrueNAS SCALE (Linux-based, Debian 12)

TrueNAS SCALE runs a customised Debian 12 (Bookworm) kernel. Use the linux-amd64 binary.

Important constraints

TrueNAS SCALE mounts the root filesystem read-only and resets /usr, /bin, /opt
on upgrades. Safe persistent locations are:

  • /mnt/<pool>/ — your ZFS pool mount (survives upgrades)
  • Custom Apps (Docker/Kubernetes) — recommended for long-running agents

Option A — Run from a ZFS dataset (simplest)

# Create a dataset for tooling
zfs create tank/tools

# Install binary and config
cp netbox-agent-linux-amd64 /mnt/tank/tools/netbox-agent
chmod 755 /mnt/tank/tools/netbox-agent
mkdir -p /mnt/tank/tools/netbox-agent-config

cat > /mnt/tank/tools/netbox-agent-config/config.yml <<EOF
netbox:
  url: "https://netbox.example.com/api/"
  token: "Bearer nbt_XXXX.YYYY"
  site: "Main DC"
device:
  physical: true
  role: "NAS"
agent:
  interval_seconds: 3600
EOF
chmod 600 /mnt/tank/tools/netbox-agent-config/config.yml

Schedule via System → Advanced → Cron Jobs in the TrueNAS UI, or create
a systemd override that survives across the TrueNAS-managed systemd:

mkdir -p /etc/systemd/system/netbox-agent.service.d
cat > /etc/systemd/system/netbox-agent.service <<EOF
[Unit]
Description=NetBox Agent
After=network-online.target zfs-mount.service
Wants=network-online.target

[Service]
Type=simple
ExecStart=/mnt/tank/tools/netbox-agent --config /mnt/tank/tools/netbox-agent-config/config.yml
Restart=on-failure
RestartSec=60

[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now netbox-agent

Note: TrueNAS SCALE may reset /etc/systemd/system/ on major version upgrades.
Check after each upgrade and re-enable if needed.

Option B — Custom App (Docker, recommended for production)

Deploy as a Custom App in Apps → Discover Apps → Custom App:

image:
  repository: your-registry/netbox-agent
  tag: latest

command: ["--once"]  # or remove for daemon mode

env:
  - name: NETBOX_AGENT_CONFIG
    value: /etc/netbox-agent/config.yml

volumeMounts:
  - mountPath: /etc/netbox-agent
    name: config

volumes:
  - name: config
    hostPath:
      path: /mnt/tank/tools/netbox-agent-config

Hardware detection on TrueNAS

  • dmidecode is available on TrueNAS SCALE and CORE — the agent will use it.
  • It will correctly detect the host's manufacturer, model, and serial number.
  • Set device.physical: true in config — TrueNAS does not run systemd-detect-virt.

What the agent reports for TrueNAS

Field Source
Hostname hostname -f
OS /etc/version.freenas (CORE) or /etc/truenas_version (SCALE)
Manufacturer / Model / Serial dmidecode (SCALE) or kenv (CORE)
Interfaces All physical NICs, VLANs, bonds — not the internal TrueNAS bridge
IP Addresses All non-link-local addresses per interface
Disks Physical block devices (note: ZFS pool members may show as individual disks)

Related Posts

SomaFM Tuner
programmingGTKlinuxmusic

SomaFM Tuner

Internet FM script tuner GTK script

3/27/2026Read More →
Custom Fiber Home Link with PFSense
linuxpfsenseinternet

Custom Fiber Home Link with PFSense

To avoid using KPN or other provider router, you can always rely on PFSense and dedicated hardware to make it yourself!

3/27/2026Read More →
Magic Mirror
linuxinternetDIYtinkeringRaspberry Pi

Magic Mirror

The Fun DIY Anyone Can Do!

3/27/2026Read More →