diff --git a/maintainers/maintainer-list.nix b/maintainers/maintainer-list.nix index 1444b852f2704b..c726173366a876 100644 --- a/maintainers/maintainer-list.nix +++ b/maintainers/maintainer-list.nix @@ -17928,6 +17928,12 @@ github = "sinanmohd"; githubId = 69694713; }; + sinavir = { + name = "Maurice Debray"; + email = "nixpkgs@sinavir.fr"; + github = "sinavir"; + githubId = 36380103; + }; sioodmy = { name = "Antoni SokoĊ‚owski"; github = "sioodmy"; diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 2ccaea466c6a75..f9668b5fc32959 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -729,6 +729,7 @@ ./services/misc/moonraker.nix ./services/misc/mqtt2influxdb.nix ./services/misc/n8n.nix + ./services/misc/netbox-agent.nix ./services/misc/nitter.nix ./services/misc/nix-gc.nix ./services/misc/nix-optimise.nix diff --git a/nixos/modules/services/misc/netbox-agent.nix b/nixos/modules/services/misc/netbox-agent.nix new file mode 100644 index 00000000000000..3972b47e685b8f --- /dev/null +++ b/nixos/modules/services/misc/netbox-agent.nix @@ -0,0 +1,80 @@ +{ config, pkgs, lib, ... }: +let + cfg = config.services.netbox-agent; + settingsFormat = pkgs.formats.json {}; +in { + options.services.netbox-agent = { + enable = lib.mkEnableOption "Netbox-agent"; + startAt = lib.mkOption { + type = with lib.types; either str (listOf str); + default = "*-*-* 00:00:00"; + description = '' + Automatically start this unit at the given date/time, which + must be in the format described in + {manpage}`systemd.time(7)`. + ''; + }; + randomizedDelaySec = lib.mkOption { + default = "0"; + type = lib.types.str; + example = "45min"; + description = '' + Add a randomized delay before each netbox-agent runs. + The delay will be chosen between zero and this value. + This value must be a time span in the format specified by + {manpage}`systemd.time(7)` + ''; + }; + settings = lib.mkOption { + type = settingsFormat.type; + description = '' + Settings to be passed to the netbox agent. Will be converted to a YAML + config file + ''; + default = {}; + }; + + environmentFile = lib.mkOption { + type = with lib.types; nullOr path; + description = '' + Environment file to pass to netbox-agent. See `netbox-agent --help` for + possible environment variables + ''; + default = null; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.netbox-agent = { + description = "Netbox-agent service. It generates an existing infrastructure on Netbox and have the ability to update it regularly through this service."; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + + serviceConfig = { + Type = "oneshot"; + # We could link directly into pkgs.tzdata, but at least timedatectl seems + # to expect the symlink to point directly to a file in etc. + # Setting the "debian timezone file" to point at /dev/null stops it doing anything. + ExecStart = "${lib.getExe pkgs.netbox-agent} -c ${settingsFormat.generate "netbox-agent-config.yaml" cfg.settings}"; + EnvironmentFile = cfg.environmentFile; + LockPersonality = true; + MemoryDenyWriteExecute=true; + NoNewPrivileges=true; + PrivateTmp=true; + ProtectControlGroups=true; + ProtectHome=true; + ProtectKernelModules=true; + ProtectKernelTunables=true; + ProtectSystem="strict"; + RestrictAddressFamilies="AF_UNIX AF_INET AF_INET6 AF_NETLINK"; + RestrictNamespaces=true; + RestrictRealtime=true; + RestrictSUIDSGID=true; + }; + inherit (cfg) startAt; + }; + systemd.timers.netbox-agent.timerConfig.RandomizedDelaySec = cfg.randomizedDelaySec; + }; + + meta.maintainers = with lib.maintainers; [ raitobezarius sinavir ]; +} diff --git a/pkgs/by-name/ne/netbox-agent/lshw.patch b/pkgs/by-name/ne/netbox-agent/lshw.patch new file mode 100644 index 00000000000000..824674895d537f --- /dev/null +++ b/pkgs/by-name/ne/netbox-agent/lshw.patch @@ -0,0 +1,87 @@ +From 116334be2f4bdae996a0d8dc623ae6e24d20e8aa Mon Sep 17 00:00:00 2001 +From: sinavir +Date: Sat, 23 Mar 2024 01:45:20 +0100 +Subject: [PATCH 1/2] fix: make lshw props finding more resilient + +--- + netbox_agent/lshw.py | 25 +++++++++++++------------ + 1 file changed, 13 insertions(+), 12 deletions(-) + +diff --git a/netbox_agent/lshw.py b/netbox_agent/lshw.py +index e88993c..c79f95e 100644 +--- a/netbox_agent/lshw.py ++++ b/netbox_agent/lshw.py +@@ -88,9 +88,9 @@ def find_network(self, obj): + "name": obj.get("logicalname", unkn_name), + "macaddress": obj.get("serial", ""), + "serial": obj.get("serial", ""), +- "product": obj["product"], +- "vendor": obj["vendor"], +- "description": obj["description"], ++ "product": obj.get("product", "Unknown NIC"), ++ "vendor": obj.get("vendor", "Unknown"), ++ "description": obj.get("description", ""), + }) + + def find_storage(self, obj): +@@ -135,10 +135,10 @@ def find_storage(self, obj): + def find_cpus(self, obj): + if "product" in obj: + self.cpus.append({ +- "product": obj["product"], +- "vendor": obj["vendor"], +- "description": obj["description"], +- "location": obj["slot"], ++ "product": obj.get("product", "Unknown CPU"), ++ "vendor": obj.get("vendor", "Unknown vendor"), ++ "description": obj.get("description", ""), ++ "location": obj.get("slot", ""), + }) + + def find_memories(self, obj): +@@ -162,11 +162,12 @@ def find_memories(self, obj): + + def find_gpus(self, obj): + if "product" in obj: +- self.gpus.append({ +- "product": obj["product"], +- "vendor": obj["vendor"], +- "description": obj["description"], +- }) ++ infos = { ++ "product": obj.get("product", "Unknown GPU"), ++ "vendor": obj.get("vendor", "Unknown"), ++ "description": obj.get("description", ""), ++ } ++ self.gpus.append(infos) + + def walk_bridge(self, obj): + if "children" not in obj: + +From f512e7a0a971013eb49f233c72963fd54a78c8ca Mon Sep 17 00:00:00 2001 +From: sinavir +Date: Wed, 27 Mar 2024 18:49:05 +0100 +Subject: [PATCH 2/2] fix: replace list.push by list.append + +--- + netbox_agent/lshw.py | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/netbox_agent/lshw.py b/netbox_agent/lshw.py +index c79f95e..876df97 100644 +--- a/netbox_agent/lshw.py ++++ b/netbox_agent/lshw.py +@@ -77,11 +77,11 @@ def find_network(self, obj): + # newer versions of lshw can return a list of names, see issue #227 + if not isinstance(i["name"], list): + if i["name"].startswith("unknown"): +- unkn_intfs.push(i) ++ unkn_intfs.append(i) + else: + for j in i["name"]: + if j.startswith("unknown"): +- unkn_intfs.push(j) ++ unkn_intfs.append(j) + + unkn_name = "unknown{}".format(len(unkn_intfs)) + self.interfaces.append({ diff --git a/pkgs/by-name/ne/netbox-agent/package.nix b/pkgs/by-name/ne/netbox-agent/package.nix new file mode 100644 index 00000000000000..c655a55bff99aa --- /dev/null +++ b/pkgs/by-name/ne/netbox-agent/package.nix @@ -0,0 +1,73 @@ +{ lib +, python3 +, fetchFromGitHub +, ethtool +, dmidecode +, ipmitool +, lldpd +, lshw +}: + +python3.pkgs.buildPythonApplication rec { + pname = "netbox-agent"; + version = "unstable-2023-03-19"; + pyproject = true; + + src = fetchFromGitHub { + owner = "Solvik"; + repo = "netbox-agent"; + rev = "master"; + hash = "sha256-lkC5UZFEN78GIbE5m6/c9iEYkynWZh+S5Kn0CuKisuM="; + }; + + patches = [ + ./lshw.patch # https://github.com/Solvik/netbox-agent/pull/292 + ./vm_tags.patch # https://github.com/Solvik/netbox-agent/pull/293 + ./role.patch # https://github.com/Solvik/netbox-agent/pull/289 + ./return-code.patch # From https://github.com/Solvik/netbox-agent/pull/279 + ]; + + postPatch = '' + # remove legacy code + echo "" > netbox_agent/__init__.py + ''; + + nativeBuildInputs = [ + python3.pkgs.setuptools + python3.pkgs.wheel + python3.pkgs.pythonRelaxDepsHook + ]; + pythonRelaxDeps = true; + + propagatedBuildInputs = with python3.pkgs; [ + distro + jsonargparse + netaddr + netifaces + packaging + pynetbox + python-slugify + pyyaml + ]; + + postInstall = '' + wrapProgram $out/bin/netbox_agent \ + --prefix PATH ":" ${lib.makeBinPath [ + ethtool + dmidecode + ipmitool + lldpd + lshw + ]} + ''; + + pythonImportsCheck = [ "netbox_agent" ]; + + meta = with lib; { + description = "Netbox agent to run on your infrastructure's servers"; + homepage = "https://github.com/Solvik/netbox-agent"; + license = licenses.asl20; + maintainers = with maintainers; [ sinavir raitobezarius ]; + mainProgram = "netbox_agent"; + }; +} diff --git a/pkgs/by-name/ne/netbox-agent/return-code.patch b/pkgs/by-name/ne/netbox-agent/return-code.patch new file mode 100644 index 00000000000000..8aaf2f830e52a7 --- /dev/null +++ b/pkgs/by-name/ne/netbox-agent/return-code.patch @@ -0,0 +1,22 @@ +From 76b2d253ce94c11d77deeaf36c5b6947ef3cd94a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Gr=C3=A9goire=20Compagnon?= +Date: Mon, 21 Aug 2023 20:40:10 +0200 +Subject: [PATCH] Return 0 if everything ok as excepted in a shell + +--- + netbox_agent/cli.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/netbox_agent/cli.py b/netbox_agent/cli.py +index e112469a..74cd8f9a 100644 +--- a/netbox_agent/cli.py ++++ b/netbox_agent/cli.py +@@ -47,7 +47,7 @@ def run(config): + + + def main(): +- return run(config) ++ return 0 if run(config) else 1 + + + if __name__ == '__main__': diff --git a/pkgs/by-name/ne/netbox-agent/role.patch b/pkgs/by-name/ne/netbox-agent/role.patch new file mode 100644 index 00000000000000..c1c00180791c1e --- /dev/null +++ b/pkgs/by-name/ne/netbox-agent/role.patch @@ -0,0 +1,36 @@ +diff --git a/netbox_agent/server.py b/netbox_agent/server.py +index c755a71..cb9e678 100644 +--- a/netbox_agent/server.py ++++ b/netbox_agent/server.py +@@ -204,6 +204,7 @@ def _netbox_create_chassis(self, datacenter, tenant, rack): + tenant=tenant.id if tenant else None, + rack=rack.id if rack else None, + tags=[{'name': x} for x in self.tags], ++ role=device_role.id, + custom_fields=self.custom_fields, + ) + return new_chassis +@@ -226,6 +227,7 @@ def _netbox_create_blade(self, chassis, datacenter, tenant, rack): + site=datacenter.id if datacenter else None, + tenant=tenant.id if tenant else None, + rack=rack.id if rack else None, ++ role=device_role.id, + tags=[{'name': x} for x in self.tags], + custom_fields=self.custom_fields, + ) +@@ -249,6 +251,7 @@ def _netbox_create_blade_expansion(self, chassis, datacenter, tenant, rack): + site=datacenter.id if datacenter else None, + tenant=tenant.id if tenant else None, + rack=rack.id if rack else None, ++ role=device_role.id, + tags=[{'name': x} for x in self.tags], + ) + return new_blade +@@ -278,6 +281,7 @@ def _netbox_create_server(self, datacenter, tenant, rack): + site=datacenter.id if datacenter else None, + tenant=tenant.id if tenant else None, + rack=rack.id if rack else None, ++ role=device_role.id, + tags=[{'name': x} for x in self.tags], + ) + return new_server diff --git a/pkgs/by-name/ne/netbox-agent/vm_tags.patch b/pkgs/by-name/ne/netbox-agent/vm_tags.patch new file mode 100644 index 00000000000000..22f805c502c00e --- /dev/null +++ b/pkgs/by-name/ne/netbox-agent/vm_tags.patch @@ -0,0 +1,53 @@ +From c9a57de843e301483a0d3030aca475697026afea Mon Sep 17 00:00:00 2001 +From: sinavir +Date: Thu, 28 Mar 2024 12:07:17 +0100 +Subject: [PATCH] fix: vm tags + +--- + netbox_agent/virtualmachine.py | 18 +++++++++++++----- + 1 file changed, 13 insertions(+), 5 deletions(-) + +diff --git a/netbox_agent/virtualmachine.py b/netbox_agent/virtualmachine.py +index b32d3bd..3b3f89c 100644 +--- a/netbox_agent/virtualmachine.py ++++ b/netbox_agent/virtualmachine.py +@@ -42,8 +42,7 @@ def __init__(self, dmi=None): + self.device_platform = get_device_platform(config.device.platform) + + self.tags = list(set(config.device.tags.split(','))) if config.device.tags else [] +- if self.tags and len(self.tags): +- create_netbox_tags(self.tags) ++ self.nb_tags = create_netbox_tags(self.tags) + + def get_memory(self): + mem_bytes = os.sysconf('SC_PAGE_SIZE') * os.sysconf('SC_PHYS_PAGES') # e.g. 4015976448 +@@ -107,7 +106,7 @@ def netbox_create_or_update(self, config): + vcpus=vcpus, + memory=memory, + tenant=tenant.id if tenant else None, +- tags=self.tags, ++ tags=[{'name': x} for x in self.tags], + ) + created = True + +@@ -121,9 +120,18 @@ def netbox_create_or_update(self, config): + if vm.memory != memory: + vm.memory = memory + updated += 1 +- if sorted(set(vm.tags)) != sorted(set(self.tags)): +- vm.tags = self.tags ++ ++ vm_tags = sorted(set([x.name for x in vm.tags])) ++ tags = sorted(set(self.tags)) ++ if vm_tags != tags: ++ new_tags_ids = [x.id for x in self.nb_tags] ++ if not config.preserve_tags: ++ vm.tags = new_tags_ids ++ else: ++ vm_tags_ids = [x.id for x in vm.tags] ++ vm.tags = sorted(set(new_tags_ids + vm_tags_ids)) + updated += 1 ++ + if vm.platform != self.device_platform: + vm.platform = self.device_platform + updated += 1