diff --git a/sys-apps-netbox-dhcp-setup.py b/sys-apps-netbox-dhcp-setup.py deleted file mode 100755 index dc8afa7..0000000 --- a/sys-apps-netbox-dhcp-setup.py +++ /dev/null @@ -1,144 +0,0 @@ -#!/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() diff --git a/sys-apps-netbox-reserve-dhcp-ips.py b/sys-apps-netbox-reserve-dhcp-ips.py deleted file mode 100755 index 74f7741..0000000 --- a/sys-apps-netbox-reserve-dhcp-ips.py +++ /dev/null @@ -1,158 +0,0 @@ -#!/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() diff --git a/sys-apps-session-summary.md b/sys-apps-session-summary.md index a8bff3e..e0f011b 100644 --- a/sys-apps-session-summary.md +++ b/sys-apps-session-summary.md @@ -105,11 +105,16 @@ First run failed because script tried to create tag "dhcp-pool" which didn't exi ## Files Created +**Scripts moved to system-apps repository:** ``` -/home/pdm/.homelab-agents/sys-apps-netbox-dhcp-setup.py -/home/pdm/.homelab-agents/sys-apps-netbox-reserve-dhcp-ips.py +http://100.120.125.113:3000/pdm/system-apps +├── sys-apps-netbox-dhcp-setup.py +├── sys-apps-netbox-reserve-dhcp-ips.py +└── README.md ``` +**Note:** Scripts were initially created in homelab-agents but moved to system-apps repository as they are VPS-specific configuration tools, not shared agents. + **Both scripts:** - Follow naming convention: `{hostname}-{description}.py` - Are executable (`chmod +x`) @@ -134,18 +139,22 @@ This provides: When adding a new VLAN, run both scripts to configure DHCP pool: ```bash +# Clone system-apps repository (if not already cloned) +git clone git@100.120.125.113:pdm/system-apps.git ~/system-apps +cd ~/system-apps + # 1. Create IP Range -python3 /home/pdm/.homelab-agents/sys-apps-netbox-dhcp-setup.py 'YOUR_API_TOKEN' +python3 sys-apps-netbox-dhcp-setup.py 'YOUR_API_TOKEN' # 2. Reserve Individual IPs -python3 /home/pdm/.homelab-agents/sys-apps-netbox-reserve-dhcp-ips.py 'YOUR_API_TOKEN' +python3 sys-apps-netbox-reserve-dhcp-ips.py 'YOUR_API_TOKEN' ``` Or set environment variable once: ```bash export NETBOX_TOKEN='YOUR_API_TOKEN' -python3 /home/pdm/.homelab-agents/sys-apps-netbox-dhcp-setup.py -python3 /home/pdm/.homelab-agents/sys-apps-netbox-reserve-dhcp-ips.py +python3 sys-apps-netbox-dhcp-setup.py +python3 sys-apps-netbox-reserve-dhcp-ips.py ``` ## Key Decisions @@ -163,11 +172,12 @@ NetBox standard status values: - **Reserved is correct** for DHCP pool (allocated to DHCP server, not assigned to specific device) ### Script Location -Placed in `~/.homelab-agents/` because: -- Infrastructure/automation tools -- Can be reused across VPS instances -- Follows established pattern (agents, scripts, tools live here) -- Prefixed with `sys-apps-` per naming convention +**Moved to `system-apps` repository** because: +- VPS-specific configuration tools (not shared agents) +- Belongs with other sys-apps infrastructure code +- `homelab-agents` = shared Claude Code agents only +- `system-apps` = VPS-specific automation and config +- Makes more sense in project-specific repo vs shared agent repo ## Next Session Recommendations