Files
homelab-agents/sys-apps-netbox-reserve-dhcp-ips.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

159 lines
4.9 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 IP Reservation Script
Marks individual IPs .100-.150 in each subnet as reserved for DHCP
"""
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-reserve-dhcp-ips.py")
print(" or: python3 sys-apps-netbox-reserve-dhcp-ips.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}
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_ip(ip_address, prefix_id):
"""Create or update individual IP as DHCP reserved"""
url = f"{NETBOX_URL}/api/ipam/ip-addresses/"
data = {
"address": f"{ip_address}/24", # CIDR notation
"status": "reserved",
"dns_name": "",
"description": "DHCP Pool - Reserved for dynamic assignment"
}
try:
response = requests.post(url, headers=headers, json=data, verify=True)
if response.status_code == 201:
return "created"
elif response.status_code == 400:
error_detail = response.json()
if "already exists" in str(error_detail).lower() or "duplicate" in str(error_detail).lower():
return "exists"
else:
return f"error: {error_detail}"
else:
return f"error: HTTP {response.status_code}"
except Exception as e:
return f"error: {e}"
def reserve_dhcp_range(prefix_str, prefix_id):
"""Reserve IPs .100-.150 in a prefix"""
try:
network = ipaddress.ip_network(prefix_str, strict=False)
# Calculate .100 and .150 addresses
base_ip = str(network.network_address)
octets = base_ip.split('.')
created = 0
exists = 0
errors = 0
# Create IPs from .100 to .150
for i in range(100, 151):
ip_octets = octets[:-1] + [str(i)]
ip_address = '.'.join(ip_octets)
# Validate IP is within network
ip_addr = ipaddress.ip_address(ip_address)
if ip_addr not in network:
continue
result = create_dhcp_ip(ip_address, prefix_id)
if result == "created":
created += 1
if created % 10 == 0: # Progress indicator
print(f" ... {created} IPs created so far")
elif result == "exists":
exists += 1
else:
errors += 1
if errors <= 3: # Show first 3 errors only
print(f"{ip_address}: {result}")
return created, exists, errors
except Exception as e:
print(f" ✗ Error processing {prefix_str}: {e}")
return 0, 0, 0
def main():
print(f"\nConnecting to NetBox: {NETBOX_URL}")
print("=" * 60)
print("This will create individual IP objects for .100-.150 in each subnet")
print("Status: Reserved | Description: DHCP Pool")
print("=" * 60)
# Get all prefixes
print("\nFetching prefixes...")
prefixes = get_all_prefixes()
# Filter to /24 subnets only (skip /16)
prefixes = [p for p in prefixes if '/24' in p['prefix']]
print(f"Found {len(prefixes)} /24 prefixes\n")
# Process each prefix
total_created = 0
total_exists = 0
total_errors = 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})")
created, exists, errors = reserve_dhcp_range(prefix_str, prefix_id)
print(f" ✓ Created: {created}, Already exists: {exists}, Errors: {errors}")
total_created += created
total_exists += exists
total_errors += errors
# Summary
print("\n" + "=" * 60)
print("Summary:")
print(f" ✓ Created: {total_created}")
print(f" Already existed: {total_exists}")
print(f" ✗ Errors: {total_errors}")
print("=" * 60)
print("\n✓ DHCP pool IPs reserved in NetBox!")
print(" IPs .100-.150 are now marked as 'Reserved' status")
print(" They will no longer show as 'Available' in the IP list")
if __name__ == "__main__":
main()