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>
145 lines
4.4 KiB
Python
Executable File
145 lines
4.4 KiB
Python
Executable File
#!/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()
|