feat(images): backend catalog + mirror model + download API/worker

Phase 1 of VM image mirror feature:
- StorageMixin.download_url (PVE download-url/aria2) + get_download_target_storages
- builtin curated cloud-image catalog (Ubuntu/Debian/Rocky/Alma, amd64+arm64)
- ImageMirror model for admin custom mirrors
- /proxmox/api/images: catalog, lxc-templates proxy, storages, download
- _do_image_download_sync worker with log-tailing progress (DeployTask)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 22:51:21 +05:00
parent 4d8cdc0143
commit 7aed5dd97e
9 changed files with 546 additions and 1 deletions

View File

@@ -0,0 +1,54 @@
from .base import *
class ImageMirror(Base):
"""
Кастомный источник образов (зеркало), добавленный администратором.
Дополняет встроенный curated-каталог (app/catalog/builtin.py).
kind:
'qcow2' — cloud-образ ВМ (url обязателен)
'vztmpl' — LXC-шаблон (url ИЛИ template — имя файла из репозитория)
"""
__tablename__ = "image_mirrors"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(150), nullable=False)
kind = Column(String(20), nullable=False, default='qcow2', index=True)
os = Column(String(50), nullable=True)
version = Column(String(50), nullable=True)
arch = Column(String(20), nullable=False, default='amd64')
url = Column(String(1000), nullable=True)
template = Column(String(255), nullable=True)
checksum = Column(String(150), nullable=True)
checksum_algorithm = Column(String(20), nullable=True)
description = Column(Text, nullable=True)
enabled = Column(Boolean, default=True, nullable=False)
created_by = Column(Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
def to_dict(self) -> dict:
return {
'id': f'mirror-{self.id}',
'mirror_id': self.id,
'source': 'mirror',
'kind': self.kind,
'os': self.os,
'version': self.version,
'arch': self.arch,
'name': self.name,
'url': self.url,
'template': self.template,
'checksum': self.checksum,
'checksum_algorithm': self.checksum_algorithm,
'description': self.description,
'enabled': self.enabled,
}
def __repr__(self):
return f"<ImageMirror(id={self.id}, name='{self.name}', kind='{self.kind}')>"