This repository has been archived on 2026-08-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Proxmox-VPS-Panel-secure/backend/app/proxmox_client.py
T

110 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import re
import time
from urllib.parse import urlparse
from proxmoxer import ProxmoxAPI
from .config import settings
def _client() -> ProxmoxAPI:
"""Создаёт Proxmox API client по отдельному токену панели."""
parsed = urlparse(settings.pve_host)
if not parsed.hostname:
raise RuntimeError("PVE_HOST имеет некорректный формат")
if "!" not in settings.pve_token_name:
raise RuntimeError("PVE_TOKEN_NAME должен иметь формат user@realm!token")
user, token_name = settings.pve_token_name.split("!", 1)
return ProxmoxAPI(
parsed.hostname,
port=parsed.port or 8006,
user=user,
token_name=token_name,
token_value=settings.pve_token_value.get_secret_value(),
verify_ssl=settings.pve_verify_ssl,
)
def _wait_task(client: ProxmoxAPI, node: str, upid: str, timeout: int = 1800) -> None:
"""Ожидает завершения задачи Proxmox с ограничением времени."""
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
task = client.nodes(node).tasks(upid).status.get()
if task.get("status") == "stopped":
if task.get("exitstatus") != "OK":
raise RuntimeError("Задача Proxmox завершилась с ошибкой")
return
time.sleep(2)
raise TimeoutError("Превышено время ожидания задачи Proxmox")
def next_vmid() -> int:
"""Запрашивает следующий свободный VMID у Proxmox."""
return int(_client().cluster.nextid.get())
def provision_vm(
source_vmid: int,
vmid: int,
name: str,
cores: int,
memory_mb: int,
disk_gb: int,
username: str,
password: str,
) -> None:
"""Клонирует cloud-init VM, настраивает ресурсы и запускает её."""
client = _client()
node = settings.pve_node
params = {"newid": vmid, "name": name, "full": 1}
if settings.pve_vm_storage:
params["storage"] = settings.pve_vm_storage
upid = client.nodes(node).qemu(source_vmid).clone.post(**params)
_wait_task(client, node, upid)
client.nodes(node).qemu(vmid).config.put(
cores=cores,
memory=memory_mb,
ciuser=username,
cipassword=password,
ipconfig0="ip=dhcp",
)
# Увеличиваем диск только если шаблон меньше заданного размера.
config = client.nodes(node).qemu(vmid).config.get()
for key in ("scsi0", "virtio0", "sata0", "ide0"):
value = config.get(key, "")
match = re.search(r"size=(\d+)G", value)
if match and disk_gb > int(match.group(1)):
client.nodes(node).qemu(vmid).resize.put(
disk=key, size=f"+{disk_gb - int(match.group(1))}G"
)
break
client.nodes(node).qemu(vmid).status.start.post()
def provision_lxc(
source_template: str,
vmid: int,
name: str,
cores: int,
memory_mb: int,
disk_gb: int,
password: str,
) -> None:
"""Создаёт LXC из CT-шаблона и запускает его с DHCP."""
client = _client()
node = settings.pve_node
upid = client.nodes(node).lxc.post(
vmid=vmid,
hostname=name,
ostemplate=source_template,
cores=cores,
memory=memory_mb,
swap=memory_mb,
rootfs=f"{settings.pve_lxc_storage}:{disk_gb}",
password=password,
net0=f"name=eth0,bridge={settings.pve_bridge},ip=dhcp",
unprivileged=1,
)
_wait_task(client, node, upid)
client.nodes(node).lxc(vmid).status.start.post()