From a6f61ad2ac13a36e49af298f22c6852db9119a39 Mon Sep 17 00:00:00 2001 From: Fredrick Amnehagen Date: Thu, 5 Feb 2026 19:24:04 +0100 Subject: [PATCH] feat: add ip module for automated free IP discovery in agent pool --- README.md | 17 +++++++++++------ infra_cli/dns.py | 23 ++++++++++++++++++++++- infra_cli/main.py | 25 +++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c9be3b5..d978e18 100644 --- a/README.md +++ b/README.md @@ -53,13 +53,18 @@ infra proxmox list-lxcs --node la-vmh-12 infra proxmox create-lxc 12150 local:vztmpl/debian-13-standard "new-app" "10.32.70.100/16" "10.32.0.1" --node la-vmh-12 ``` -### 3. Networking (DNS & DHCP) -```bash -# Register the new machine in DHCP -infra dns add-host "aa:bb:cc:dd:ee:ff" "10.32.70.100" "new-app" +### 3. Networking (IP, DNS & DHCP) +Assign a static identity to your new machine. The CLI helps you find free addresses in the dedicated agent pool (`10.32.70.0/24`). -# Add a custom DNS record -infra dns add-dns "api.loopaware.com" "10.32.70.100" +```bash +# Find the next available IP for your project +infra ip next-free + +# List top 5 available IPs +infra ip list-free --count 5 + +# Register the machine in DHCP +infra dns add-host "aa:bb:cc:dd:ee:ff" "10.32.70.100" "new-app" ``` ### 4. Cloudflare DDNS diff --git a/infra_cli/dns.py b/infra_cli/dns.py index caf87a9..079ee9f 100644 --- a/infra_cli/dns.py +++ b/infra_cli/dns.py @@ -55,4 +55,25 @@ class DNSManager: def list(self): hosts = self.exec_lxc(f"cat {self.hosts_file}").stdout dns = self.exec_lxc(f"cat {self.dns_file}").stdout - return {"hosts": hosts, "dns": dns} \ No newline at end of file + return {"hosts": hosts, "dns": dns} + + def get_free_ips(self, range_start=1, range_end=254, subnet="10.32.70"): + """Finds free IPs in the specified range by checking both static and dynamic leases""" + # 1. Get all static IPs from dhcp-hosts.conf and dynamic-hosts.conf + static_configs = self.exec_lxc(f"cat /etc/dnsmasq.d/dhcp-hosts.conf {self.hosts_file} 2>/dev/null").stdout + import re + used_ips = set(re.findall(r'[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}', static_configs)) + + # 2. Get all active dynamic leases + leases = self.exec_lxc("cat /var/lib/misc/dnsmasq.leases 2>/dev/null").stdout + used_ips.update(set(re.findall(r'[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}', leases))) + + # 3. Find first available in the agent range + free_ips = [] + for i in range(range_start, range_end + 1): + candidate = f"{subnet}.{i}" + if candidate not in used_ips: + free_ips.append(candidate) + if len(free_ips) >= 10: # Just return the top 10 + break + return free_ips \ No newline at end of file diff --git a/infra_cli/main.py b/infra_cli/main.py index 93a388e..3269af6 100644 --- a/infra_cli/main.py +++ b/infra_cli/main.py @@ -162,6 +162,31 @@ def dns_list(config): click.echo(data['hosts']) click.echo(data['dns']) +@cli.group() +def ip(): + """Manage and discover available IP addresses""" + pass + +@ip.command(name='list-free') +@click.option('--count', default=5, help='Number of free IPs to show') +@click.pass_obj +def ip_list_free(config, count): + mgr = DNSManager(config) + free = mgr.get_free_ips() + for ip in free[:count]: + click.echo(ip) + +@ip.command(name='next-free') +@click.pass_obj +def ip_next_free(config): + mgr = DNSManager(config) + free = mgr.get_free_ips() + if free: + click.echo(free[0]) + else: + click.echo("Error: No free IPs in agent pool!", err=True) + sys.exit(1) + @cli.group() def ingress(): """Manage HAProxy Ingress"""