Files
pvemanager/backend/app/appstore/cli.py
markmorado 0606e8ac77
All checks were successful
Backend Tests / pytest (Python 3.12) (push) Successful in 1m4s
Deploy to Production / Trigger production update (push) Successful in 12s
feat(appstore): модуль «App Store» — каталог self-hosted приложений (1 app = 1 LXC + Docker Compose)
Полный цикл M0–M5 по ТЗ:
- Catalog Service: импорт runtipi/runtipi-appstore → catalog_apps, идемпотентный
  синк, кэш логотипов, авто раз в 24ч (APScheduler), флаг update_available.
- App Engine: установка (клон золотого шаблона → pct push compose/.env →
  docker compose up → health-check) с журналом шагов, шифрованием env (Fernet),
  генерацией random-секретов и прогрессом по WebSocket.
- Lifecycle: start/stop/restart/logs/delete + реконсиляция состояния (orphaned).
- Update/Rollback через снапшоты Proxmox (pre-update, хранение последнего 1).
- UI: экраны App Store (сетка/поиск/категории), страница приложения, мастер
  установки (динамическая form_fields + advanced + прогресс + креденшелы),
  My Apps; i18n ru/en; атрибуция Runtipi.
- RBAC app:view/install/manage; только unprivileged LXC; pct exec/push без
  shell-инъекций (base64 + экранирование).
- Модели catalog_apps/installed_apps/app_operations (миграции 31–32).
- Docs: golden-template, appstore-poc/catalog/engine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:05:20 +05:00

124 lines
4.3 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.
"""
Ручной раннер M0 PoC. Запускается в окружении бэкенда (с доступом к БД панели):
# установка тестового приложения из золотого шаблона:
python -m app.appstore.cli install \
--server-id 1 \
--ostemplate local:vztmpl/golden-docker_12_amd64.tar.zst \
--name poc-uptime-kuma \
--port 3001
# повторяемость — снести контейнер:
python -m app.appstore.cli teardown --server-id 1 --vmid 131
По умолчанию берётся встроенный sample uptime-kuma (samples/uptime-kuma.compose.yml).
Свой compose/env — через --compose-file / --env-file.
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
from ..db import SessionLocal
from .pipeline import InstallSpec, PocError, poc_install, poc_teardown
_SAMPLES = Path(__file__).parent / "samples"
def _read(path: Path) -> str:
return path.read_text(encoding="utf-8")
def _parse_env(text: str) -> dict:
env = {}
for line in text.splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
k, v = line.split("=", 1)
env[k.strip()] = v.strip()
return env
def cmd_install(args) -> int:
compose_path = Path(args.compose_file) if args.compose_file else _SAMPLES / "uptime-kuma.compose.yml"
env_path = Path(args.env_file) if args.env_file else _SAMPLES / "uptime-kuma.env"
compose_yaml = _read(compose_path)
env_vars = _parse_env(_read(env_path)) if env_path.exists() else {}
spec = InstallSpec(
server_id=args.server_id,
ostemplate=args.ostemplate,
name=args.name,
compose_yaml=compose_yaml,
port=args.port,
env_vars=env_vars,
node=args.node,
cores=args.cores,
memory=args.memory,
disk=args.disk,
storage=args.storage,
bridge=args.bridge,
)
db = SessionLocal()
try:
res = poc_install(db, spec)
finally:
db.close()
print("\n=== PoC install OK ===")
print(f"vmid : {res.vmid}")
print(f"node : {res.node}")
print(f"ip : {res.ip}")
print(f"url : {res.url}")
print(f"status : {res.status}")
return 0 if res.status == "running" else 2
def cmd_teardown(args) -> int:
db = SessionLocal()
try:
poc_teardown(db, args.server_id, args.vmid, args.node)
finally:
db.close()
print(f"Teardown OK: vmid={args.vmid}")
return 0
def main(argv=None) -> int:
p = argparse.ArgumentParser(prog="app.appstore.cli", description="App Store M0 PoC runner")
sub = p.add_subparsers(dest="cmd", required=True)
pi = sub.add_parser("install", help="Установить тестовое приложение (clone→push→exec→health)")
pi.add_argument("--server-id", type=int, required=True)
pi.add_argument("--ostemplate", required=True, help="Золотой vztmpl, напр. local:vztmpl/golden-docker_12_amd64.tar.zst")
pi.add_argument("--name", default="poc-uptime-kuma")
pi.add_argument("--port", type=int, default=3001)
pi.add_argument("--node", default=None)
pi.add_argument("--cores", type=int, default=2)
pi.add_argument("--memory", type=int, default=2048)
pi.add_argument("--disk", type=int, default=8)
pi.add_argument("--storage", default="local-lvm")
pi.add_argument("--bridge", default="vmbr0")
pi.add_argument("--compose-file", default=None, help="Свой docker-compose.yml (по умолчанию sample uptime-kuma)")
pi.add_argument("--env-file", default=None, help="Свой .env (по умолчанию sample)")
pi.set_defaults(func=cmd_install)
pt = sub.add_parser("teardown", help="Остановить и удалить контейнер PoC")
pt.add_argument("--server-id", type=int, required=True)
pt.add_argument("--vmid", type=int, required=True)
pt.add_argument("--node", default=None)
pt.set_defaults(func=cmd_teardown)
args = p.parse_args(argv)
try:
return args.func(args)
except PocError as e:
print(f"\n[PoC FAILED] {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
raise SystemExit(main())