feat: add samba and proxmox modules, update config with node secrets
This commit is contained in:
parent
a80be5d8aa
commit
9c8c771cb1
7 changed files with 203 additions and 6 deletions
|
|
@ -12,12 +12,12 @@ class Config:
|
|||
if not os.path.exists(self.path):
|
||||
# Fallback to local config if exists
|
||||
if os.path.exists("config.yaml"):
|
||||
self.path = "config.yaml"
|
||||
self.path = os.path.abspath("config.yaml")
|
||||
else:
|
||||
raise FileNotFoundError(f"Config file not found at {self.path}. Please create it based on config.yaml.example")
|
||||
|
||||
with open(self.path, 'r') as f:
|
||||
return yaml.safe_vars(f) if hasattr(yaml, 'safe_vars') else yaml.safe_load(f)
|
||||
return yaml.safe_load(f)
|
||||
|
||||
def get(self, key, default=None):
|
||||
parts = key.split('.')
|
||||
|
|
@ -28,3 +28,20 @@ class Config:
|
|||
else:
|
||||
return default
|
||||
return val
|
||||
|
||||
def get_node(self, node_name):
|
||||
"""Helper to get proxmox node details by name or default to first if none provided"""
|
||||
nodes = self.get('proxmox.nodes', {})
|
||||
if not nodes:
|
||||
# Fallback for old single-host config if present
|
||||
host = self.get('proxmox.host')
|
||||
if host:
|
||||
return {"host": host, "pass": self.get('proxmox.password')}
|
||||
return None
|
||||
|
||||
if not node_name:
|
||||
# Default to first node found
|
||||
return next(iter(nodes.values()))
|
||||
|
||||
return nodes.get(node_name)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,15 @@ from .ssh import SSHClient
|
|||
|
||||
class DNSManager:
|
||||
def __init__(self, config):
|
||||
self.pve_host = config.get('proxmox.host')
|
||||
# DNS is on la-vmh-11 (dnsmasq_lxc_id)
|
||||
node = config.get_node('la-vmh-11')
|
||||
if not node:
|
||||
raise ValueError("Node 'la-vmh-11' not found in config")
|
||||
|
||||
self.pve_host = node['host']
|
||||
self.pve_user = config.get('proxmox.user', 'root')
|
||||
self.lxc_id = config.get('proxmox.dnsmasq_lxc_id')
|
||||
self.ssh_key = config.get('proxmox.ssh_key')
|
||||
self.ssh_key = config.get('proxmox.ssh_key_path')
|
||||
self.client = SSHClient(self.pve_host, self.pve_user, self.ssh_key)
|
||||
|
||||
self.hosts_file = "/etc/dnsmasq.d/dynamic-hosts.conf"
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ class IngressManager:
|
|||
def __init__(self, config):
|
||||
self.host = config.get('haproxy.host')
|
||||
self.user = config.get('haproxy.user', 'root')
|
||||
self.ssh_key = config.get('haproxy.ssh_key')
|
||||
self.client = SSHClient(self.host, self.user, self.ssh_key)
|
||||
self.ssh_key = config.get('haproxy.ssh_key_path')
|
||||
self.password = config.get('haproxy.password')
|
||||
self.client = SSHClient(self.host, self.user, self.ssh_key, self.password)
|
||||
|
||||
def add(self, domain, ip, port, https=False):
|
||||
cmd = f"ingress-manager add {domain} {ip} {port}"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ from .config import Config
|
|||
from .dns import DNSManager
|
||||
from .ingress import IngressManager
|
||||
from .router import RouterManager
|
||||
from .proxmox import ProxmoxManager
|
||||
from .samba import SambaManager
|
||||
import sys
|
||||
|
||||
@click.group()
|
||||
|
|
@ -16,6 +18,61 @@ def cli(ctx, config):
|
|||
click.echo(f"Error: {e}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
@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"""
|
||||
|
|
|
|||
52
infra_cli/proxmox.py
Normal file
52
infra_cli/proxmox.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
from .ssh import SSHClient
|
||||
|
||||
class ProxmoxManager:
|
||||
def __init__(self, config, node_name=None):
|
||||
node = config.get_node(node_name)
|
||||
if not node:
|
||||
raise ValueError(f"Node '{node_name}' not found in config")
|
||||
|
||||
self.node_name = node_name
|
||||
self.host = node['host']
|
||||
self.password = node.get('pass')
|
||||
self.user = config.get('proxmox.user', 'root')
|
||||
self.ssh_key = config.get('proxmox.ssh_key_path')
|
||||
self.client = SSHClient(self.host, self.user, self.ssh_key, self.password)
|
||||
|
||||
def list_lxcs(self):
|
||||
res = self.client.run("pct list")
|
||||
return res.stdout
|
||||
|
||||
def list_vms(self):
|
||||
res = self.client.run("qm list")
|
||||
return res.stdout
|
||||
|
||||
def status_lxc(self, vmid):
|
||||
res = self.client.run(f"pct status {vmid}")
|
||||
return res.stdout
|
||||
|
||||
def create_lxc(self, vmid, template, hostname, ip, gateway, bridge="vmbr0", storage="local-zfs", password=None):
|
||||
# Professional creation command with sane defaults
|
||||
cmd = f"pct create {vmid} {template} --hostname {hostname} " \
|
||||
f"--net0 name=eth0,bridge={bridge},ip={ip},gw={gateway} " \
|
||||
f"--storage {storage} --onboot 1"
|
||||
|
||||
if password:
|
||||
cmd += f" --password {password}"
|
||||
|
||||
res = self.client.run(cmd)
|
||||
if res.returncode != 0:
|
||||
raise RuntimeError(f"Failed to create LXC {vmid}: {res.stderr}")
|
||||
return res.stdout
|
||||
|
||||
def start_lxc(self, vmid):
|
||||
return self.client.run(f"pct start {vmid}")
|
||||
|
||||
def stop_lxc(self, vmid):
|
||||
return self.client.run(f"pct stop {vmid}")
|
||||
|
||||
def delete_lxc(self, vmid):
|
||||
res = self.client.run(f"pct destroy {vmid}")
|
||||
if res.returncode != 0:
|
||||
raise RuntimeError(f"Failed to destroy LXC {vmid}: {res.stderr}")
|
||||
return res.stdout
|
||||
42
infra_cli/samba.py
Normal file
42
infra_cli/samba.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
from .ssh import SSHClient
|
||||
|
||||
class SambaManager:
|
||||
def __init__(self, config):
|
||||
# Samba is on la-samba-01 (handled via Jump host or direct IP)
|
||||
# In this infra, we often use pct exec on the host vmh-11
|
||||
node = config.get_node('la-vmh-11')
|
||||
if not node:
|
||||
raise ValueError("Node 'la-vmh-11' not found in config")
|
||||
|
||||
self.host = node['host']
|
||||
self.password = node.get('pass')
|
||||
self.user = config.get('proxmox.user', 'root')
|
||||
self.ssh_key = config.get('proxmox.ssh_key_path')
|
||||
self.client = SSHClient(self.host, self.user, self.ssh_key, self.password)
|
||||
|
||||
# Container ID for la-samba-01
|
||||
self.lxc_id = "1113"
|
||||
|
||||
def exec_samba(self, cmd):
|
||||
# Executes samba-tool inside the LXC
|
||||
return self.client.run(f"pct exec {self.lxc_id} -- samba-tool {cmd}")
|
||||
|
||||
def list_users(self):
|
||||
res = self.exec_samba("user list")
|
||||
return res.stdout
|
||||
|
||||
def add_user(self, username, password):
|
||||
res = self.exec_samba(f"user create {username} {password}")
|
||||
if res.returncode != 0:
|
||||
raise RuntimeError(f"Failed to create user {username}: {res.stderr}")
|
||||
return res.stdout
|
||||
|
||||
def add_to_group(self, group, username):
|
||||
res = self.exec_samba(f"group addmembers {group} {username}")
|
||||
if res.returncode != 0:
|
||||
raise RuntimeError(f"Failed to add {username} to {group}: {res.stderr}")
|
||||
return res.stdout
|
||||
|
||||
def list_group_members(self, group):
|
||||
res = self.exec_samba(f"group listmembers {group}")
|
||||
return res.stdout
|
||||
|
|
@ -44,6 +44,29 @@ def test_ingress_cli(unique_id):
|
|||
res = run_infra(["ingress", "remove", domain])
|
||||
assert res.returncode == 0
|
||||
|
||||
def test_samba_cli(unique_id):
|
||||
username = f"testuser_{unique_id}"
|
||||
password = "TestPassword123!"
|
||||
|
||||
# List (verify we can connect)
|
||||
res = run_infra(["samba", "list-users"])
|
||||
assert res.returncode == 0
|
||||
|
||||
# Add User
|
||||
res = run_infra(["samba", "add-user", username, password])
|
||||
assert res.returncode == 0
|
||||
assert username in run_infra(["samba", "list-users"]).stdout
|
||||
|
||||
# Add to Group
|
||||
res = run_infra(["samba", "add-to-group", "xmpp-users", username])
|
||||
assert res.returncode == 0
|
||||
|
||||
def test_proxmox_cli(unique_id):
|
||||
# List LXCs on a specific node
|
||||
res = run_infra(["proxmox", "list-lxcs", "--node", "la-vmh-11"])
|
||||
assert res.returncode == 0
|
||||
assert "la-dnsmasq-01" in res.stdout or "11209" in res.stdout
|
||||
|
||||
def test_router_cli(unique_id):
|
||||
name = f"Test-Cli-{unique_id}"
|
||||
section = name.lower().replace("-", "_")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue