import click from .config import Config from .dns import DNSManager from .ingress import IngressManager from .router import RouterManager from .proxmox import ProxmoxManager from .samba import SambaManager from .cloudflare import CloudflareManager import sys @click.group() @click.option('--config', help='Path to config file') @click.pass_context def cli(ctx, config): """LoopAware Infrastructure Management CLI""" try: ctx.obj = Config(config) except Exception as e: click.echo(f"Error: {e}", err=True) sys.exit(1) @cli.group() def cloudflare(): """Manage Cloudflare DNS and DDNS""" pass @cloudflare.command(name='list-ddns') @click.pass_obj def cf_list_ddns(config): mgr = CloudflareManager(config) for domain in mgr.list_domains(): click.echo(domain) @cloudflare.command(name='update-ddns') @click.option('--force', is_flag=True, help='Force update even if IP matches') @click.pass_obj def cf_update_ddns(config, force): mgr = CloudflareManager(config) click.echo("Updating Cloudflare DDNS records...") click.echo(mgr.update_ddns(force)) @cli.group() def proxmox(): """Manage Proxmox VMs and Containers""" pass @proxmox.command(name='list-lxcs') @click.option('--node', help='Proxmox node name') @click.pass_obj def proxmox_list_lxcs(config, node): mgr = ProxmoxManager(config, node) click.echo(mgr.list_lxcs()) @proxmox.command(name='create-lxc') @click.argument('vmid') @click.argument('template') @click.argument('hostname') @click.argument('ip') @click.argument('gateway') @click.option('--node', help='Proxmox node name') @click.option('--password', help='Root password for LXC') @click.pass_obj def proxmox_create_lxc(config, vmid, template, hostname, ip, gateway, node, password): mgr = ProxmoxManager(config, node) mgr.create_lxc(vmid, template, hostname, ip, gateway, password=password) click.echo(f"LXC {vmid} ({hostname}) created on {mgr.node_name or 'default node'}") @cli.group() def samba(): """Manage Samba AD Identity""" pass @samba.command(name='list-users') @click.pass_obj def samba_list_users(config): mgr = SambaManager(config) click.echo(mgr.list_users()) @samba.command(name='add-user') @click.argument('username') @click.argument('password') @click.pass_obj def samba_add_user(config, username, password): mgr = SambaManager(config) mgr.add_user(username, password) click.echo(f"User {username} created") @samba.command(name='add-to-group') @click.argument('group') @click.argument('username') @click.pass_obj def samba_add_to_group(config, group, username): mgr = SambaManager(config) mgr.add_to_group(group, username) click.echo(f"User {username} added to group {group}") @cli.group() def dns(): """Manage DNS and DHCP""" pass @dns.command(name='add-host') @click.argument('mac') @click.argument('ip') @click.argument('hostname') @click.pass_obj def dns_add_host(config, mac, ip, hostname): mgr = DNSManager(config) mgr.add_host(mac, ip, hostname) click.echo(f"Added host {hostname} ({ip})") @dns.command(name='remove-host') @click.argument('mac') @click.pass_obj def dns_remove_host(config, mac): mgr = DNSManager(config) mgr.remove_host(mac) click.echo(f"Removed host {mac}") @dns.command(name='add-dns') @click.argument('domain') @click.argument('ip') @click.pass_obj def dns_add_dns(config, domain, ip): mgr = DNSManager(config) mgr.add_dns(domain, ip) click.echo(f"Added DNS record for {domain} -> {ip}") @dns.command(name='remove-dns') @click.argument('domain') @click.pass_obj def dns_remove_dns(config, domain): mgr = DNSManager(config) mgr.remove_dns(domain) click.echo(f"Removed DNS record for {domain}") @dns.command(name='list') @click.pass_obj def dns_list(config): mgr = DNSManager(config) data = mgr.list() click.echo(data['hosts']) click.echo(data['dns']) @cli.group() def ingress(): """Manage HAProxy Ingress""" pass @ingress.command(name='add') @click.argument('domain') @click.argument('ip') @click.argument('port', type=int) @click.option('--https', is_flag=True, help='Target uses HTTPS') @click.pass_obj def ingress_add(config, domain, ip, port, https): mgr = IngressManager(config) mgr.add(domain, ip, port, https) click.echo(f"Added ingress for {domain}") @ingress.command(name='remove') @click.argument('domain') @click.pass_obj def ingress_remove(config, domain): mgr = IngressManager(config) mgr.remove(domain) click.echo(f"Removed ingress for {domain}") @ingress.command(name='list') @click.pass_obj def ingress_list(config): mgr = IngressManager(config) click.echo(mgr.list()) @cli.group() def router(): """Manage Router Port Forwards""" pass @router.command(name='add') @click.argument('name') @click.argument('proto') @click.argument('ext_port') @click.argument('int_ip') @click.argument('int_port') @click.pass_obj def router_add(config, name, proto, ext_port, int_ip, int_port): import ipaddress # Validate IP and Ports in CLI layer for better error messages try: ipaddress.ip_address(int_ip) except ValueError: raise click.BadParameter(f"Invalid internal IP address: {int_ip}") for p in [ext_port, int_port]: if not (1 <= int(p) <= 65535): raise click.BadParameter(f"Port {p} out of range (1-65535)") mgr = RouterManager(config) mgr.add_forward(name, proto, ext_port, int_ip, int_port) click.echo(f"Added port forward {name}") @router.command(name='remove') @click.argument('section') @click.pass_obj def router_remove(config, section): mgr = RouterManager(config) try: mgr.remove_forward(section) click.echo(f"Removed port forward {section}") except ValueError as e: click.echo(f"Error: {e}", err=True) sys.exit(1) @router.command(name='list') @click.pass_obj def router_list(config): mgr = RouterManager(config) for rule in mgr.list(): click.echo(f"[{rule['section']}] {rule['name']}: {rule['proto']} {rule['port']} -> {rule['dest']}") def main(): cli(obj={}) if __name__ == "__main__": main()