Files
homelab-agents/sys-apps-netbox-dhcp-setup.py
Homelab Automation f888cec027 Homelab: [homelab-agents] - Session 2025-11-30
Added NetBox DHCP pool configuration automation scripts. Created tools to reserve DHCP pool IP ranges (.100-.150) via NetBox API, preventing them from showing as "available" for static assignment.

Changes:
- Created sys-apps-netbox-dhcp-setup.py: Creates IP Range objects for DHCP pools
- Created sys-apps-netbox-reserve-dhcp-ips.py: Reserves individual IPs as "Reserved" status
- Updated sys-apps-session-summary.md with comprehensive session documentation
- Updated CLAUDE.md with session 2025-11-30 history entry
- Configured 255 IPs across 5 VLANs (51 IPs per /24 subnet)
- Established DHCP pool standard: .100-.150 for all networks

Repository: http://100.120.125.113:3000/pdm/homelab-agents
Next Session Focus: Document NetBox token management and create dhcp-pool tag

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 13:28:48 +00:00

145 lines
4.4 KiB
Python
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
NetBox DHCP Pool Setup Script
Marks IPs .100-.150 in each subnet as DHCP pool range
"""
import requests
import ipaddress
import sys
import os
# Configuration
NETBOX_URL = "https://netbox.pdmarf.co.uk"
NETBOX_TOKEN = os.environ.get("NETBOX_TOKEN") or sys.argv[1] if len(sys.argv) > 1 else None
if not NETBOX_TOKEN:
print("Error: NetBox API token required")
print("Usage: NETBOX_TOKEN='your-token' python3 sys-apps-netbox-dhcp-setup.py")
print(" or: python3 sys-apps-netbox-dhcp-setup.py 'your-token'")
sys.exit(1)
# API headers
headers = {
"Authorization": f"Token {NETBOX_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json"
}
def get_all_prefixes():
"""Get all IPv4 prefixes from NetBox"""
url = f"{NETBOX_URL}/api/ipam/prefixes/"
params = {"family": 4, "limit": 1000} # IPv4 only
try:
response = requests.get(url, headers=headers, params=params, verify=True)
response.raise_for_status()
return response.json()["results"]
except requests.exceptions.RequestException as e:
print(f"Error fetching prefixes: {e}")
sys.exit(1)
def create_dhcp_range(prefix_str, prefix_id):
"""Create DHCP pool IP range (.100-.150) for a prefix"""
try:
network = ipaddress.ip_network(prefix_str, strict=False)
# Calculate .100 and .150 addresses
# For /24: x.x.x.100 and x.x.x.150
# For other sizes, adjust accordingly
base_ip = str(network.network_address)
octets = base_ip.split('.')
# Start: .100
start_octets = octets[:-1] + ['100']
start_ip = '.'.join(start_octets)
# End: .150
end_octets = octets[:-1] + ['150']
end_ip = '.'.join(end_octets)
# Validate IPs are within the network
start_addr = ipaddress.ip_address(start_ip)
end_addr = ipaddress.ip_address(end_ip)
if start_addr not in network or end_addr not in network:
print(f" ⚠️ Skipping {prefix_str}: DHCP range (.100-.150) not within network")
return False
# Create IP Range via API
url = f"{NETBOX_URL}/api/ipam/ip-ranges/"
data = {
"start_address": start_ip,
"end_address": end_ip,
"status": "active",
"description": "DHCP Pool - Dynamic Assignment Range (.100-.150)",
"comments": "Auto-created: IPs reserved for DHCP dynamic assignment"
}
response = requests.post(url, headers=headers, json=data, verify=True)
if response.status_code == 201:
print(f" ✓ Created DHCP range: {start_ip} - {end_ip}")
return True
elif response.status_code == 400:
error_detail = response.json()
if "already exists" in str(error_detail).lower() or "overlap" in str(error_detail).lower():
print(f" Range already exists: {start_ip} - {end_ip}")
return True
else:
print(f" ✗ Failed: {error_detail}")
return False
else:
print(f" ✗ HTTP {response.status_code}: {response.text}")
return False
except Exception as e:
print(f" ✗ Error processing {prefix_str}: {e}")
return False
def main():
print(f"\nConnecting to NetBox: {NETBOX_URL}")
print("=" * 60)
# Get all prefixes
print("\nFetching prefixes...")
prefixes = get_all_prefixes()
print(f"Found {len(prefixes)} prefixes\n")
# Process each prefix
success_count = 0
skip_count = 0
fail_count = 0
for prefix in prefixes:
prefix_str = prefix["prefix"]
prefix_id = prefix["id"]
vlan_name = prefix.get("vlan", {}).get("name", "No VLAN") if prefix.get("vlan") else "No VLAN"
print(f"Processing: {prefix_str} ({vlan_name})")
result = create_dhcp_range(prefix_str, prefix_id)
if result:
success_count += 1
elif result is None:
skip_count += 1
else:
fail_count += 1
# Summary
print("\n" + "=" * 60)
print("Summary:")
print(f" ✓ Created/Exists: {success_count}")
print(f" ⚠️ Skipped: {skip_count}")
print(f" ✗ Failed: {fail_count}")
print("=" * 60)
print("\n✓ DHCP pool ranges configured!")
print(" IPs .100-.150 are now marked as DHCP pool in NetBox")
print(f" View at: {NETBOX_URL}/ipam/ip-ranges/")
if __name__ == "__main__":
main()