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>
This commit is contained in:
Homelab Automation
2025-11-30 13:28:48 +00:00
parent 4c3f40506d
commit f888cec027
4 changed files with 476 additions and 132 deletions

View File

@@ -0,0 +1,158 @@
#!/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()