Обновлён Gitea Manager (Proxmox) v1.4.0 — суффикс _proxmox для всех методов

This commit is contained in:
2026-08-16 15:41:37 +03:00
parent 89f6345923
commit 6e19b8ee05
+48 -47
View File
@@ -1,8 +1,8 @@
""" """
title: Gitea Manager Proxmox title: Gitea Manager (Proxmox)
author: Claude author: Claude
version: 1.3.0 version: 1.4.0
description: Инструмент для управления второй Gitea (Proxmox, 192.168.31.57:3000) из Open WebUI — создание и удаление репозиториев, организаций, файлов, веток, загрузка и безопасная распаковка ZIP-архивов. description: Инструмент для управления Gitea на Proxmox (192.168.31.57:3000) из Open WebUI — создание и удаление репозиториев, организаций, файлов, веток, загрузка и безопасная распаковка ZIP-архивов. Все методы имеют суффикс _proxmox, чтобы не путаться с инструментом для основной Gitea.
requirements: requests requirements: requests
""" """
import base64 import base64
@@ -20,13 +20,17 @@ from pydantic import BaseModel, Field
class Tools: class Tools:
"""Инструмент управления Gitea (Proxmox) и локальными файлами рабочего пространства.""" """Инструмент управления Gitea на PROXMOX (192.168.31.57:3000) и локальными файлами рабочего пространства.
ВАЖНО: это инструмент для Gitea на Proxmox. Для основного сервера Gitea (192.168.31.100)
используется отдельный инструмент "Gitea Manager (Main)" с суффиксом _proxmox у методов.
"""
class Valves(BaseModel): class Valves(BaseModel):
GITEA_URL: str = Field(default="http://192.168.31.57:3000", description="Базовый URL Gitea (Proxmox) без слэша в конце.") GITEA_URL: str = Field(default="http://192.168.31.57:3000", description="Базовый URL Gitea на Proxmox без слэша в конце.")
GITEA_TOKEN: str = Field(default="", description="API-токен Gitea. Заполните в настройках инструмента.") GITEA_TOKEN: str = Field(default="", description="API-токен Gitea на Proxmox. Заполните в настройках инструмента.")
TIMEOUT: int = Field(default=15, description="Таймаут запросов в секундах.") TIMEOUT: int = Field(default=15, description="Таймаут запросов в секундах.")
ENABLED: bool = Field(default=True, description="Включить инструмент Gitea Manager Proxmox.") ENABLED: bool = Field(default=True, description="Включить инструмент Gitea Manager (Proxmox).")
WORKING_DIRECTORY: str = Field(default="/tmp/gitea-manager-proxmox-workdir", description="Корневая рабочая директория.") WORKING_DIRECTORY: str = Field(default="/tmp/gitea-manager-proxmox-workdir", description="Корневая рабочая директория.")
MAX_ARCHIVE_FILES: int = Field(default=10000, description="Максимальное количество элементов ZIP.") MAX_ARCHIVE_FILES: int = Field(default=10000, description="Максимальное количество элементов ZIP.")
MAX_ARCHIVE_SIZE_MB: int = Field(default=2048, description="Максимальный распакованный размер ZIP в МБ.") MAX_ARCHIVE_SIZE_MB: int = Field(default=2048, description="Максимальный распакованный размер ZIP в МБ.")
@@ -35,15 +39,15 @@ class Tools:
self.valves = self.Valves() self.valves = self.Valves()
def _check(self) -> Optional[str]: def _check(self) -> Optional[str]:
"""Проверить доступность инструмента и наличие токена.""" """Проверить доступность инструмента и наличие токена (Gitea на Proxmox)."""
if not self.valves.ENABLED: if not self.valves.ENABLED:
return "Инструмент Gitea Manager Proxmox выключен." return "Инструмент Gitea Manager (Proxmox) выключен."
if not self.valves.GITEA_TOKEN: if not self.valves.GITEA_TOKEN:
return "API-токен Gitea не задан в настройках инструмента." return "API-токен Gitea на Proxmox не задан в настройках инструмента."
return None return None
def _request(self, method: str, path: str, **kwargs) -> str: def _request(self, method: str, path: str, **kwargs) -> str:
"""Выполнить запрос к Gitea (Proxmox) и вернуть JSON или сообщение об ошибке.""" """Выполнить запрос к Gitea на Proxmox (192.168.31.57) и вернуть JSON или сообщение об ошибке."""
error = self._check() error = self._check()
if error: if error:
return error return error
@@ -60,17 +64,17 @@ class Tools:
return f"Ошибка {response.status_code}: {response.text}" return f"Ошибка {response.status_code}: {response.text}"
return response.text or "Операция выполнена успешно." return response.text or "Операция выполнена успешно."
except requests.RequestException as exc: except requests.RequestException as exc:
return f"Ошибка соединения с Gitea (Proxmox): {exc}" return f"Ошибка соединения с Gitea на Proxmox (192.168.31.57): {exc}"
def create_repository(self, name: str, description: str = "", private: bool = False, auto_init: bool = True) -> str: def create_repository_proxmox(self, name: str, description: str = "", private: bool = False, auto_init: bool = True) -> str:
"""Создать репозиторий текущего пользователя.""" """Создать репозиторий текущего пользователя на Gitea на PROXMOX (192.168.31.57)."""
return self._request("POST", "/user/repos", json={"name": name, "description": description, "private": private, "auto_init": auto_init}) return self._request("POST", "/user/repos", json={"name": name, "description": description, "private": private, "auto_init": auto_init})
def create_repository_in_org(self, org: str, name: str, description: str = "", private: bool = False, auto_init: bool = True) -> str: def create_repository_in_org_proxmox(self, org: str, name: str, description: str = "", private: bool = False, auto_init: bool = True) -> str:
"""Создать репозиторий внутри организации.""" """Создать репозиторий внутри организации на Gitea на PROXMOX (192.168.31.57)."""
return self._request("POST", f"/orgs/{quote(org, safe='')}/repos", json={"name": name, "description": description, "private": private, "auto_init": auto_init}) return self._request("POST", f"/orgs/{quote(org, safe='')}/repos", json={"name": name, "description": description, "private": private, "auto_init": auto_init})
def list_repositories( def list_repositories_proxmox(
self, self,
limit: int = 50, limit: int = 50,
page: int = 1, page: int = 1,
@@ -78,7 +82,7 @@ class Tools:
fields: Optional[str] = "name,private,language,html_url,description,updated_at,default_branch", fields: Optional[str] = "name,private,language,html_url,description,updated_at,default_branch",
) -> str: ) -> str:
""" """
Получить список репозиториев текущего пользователя в компактном виде. Получить список репозиториев текущего пользователя на Gitea на PROXMOX (192.168.31.57) в компактном виде.
По умолчанию возвращает не весь JSON Gitea, а только краткий набор полей — По умолчанию возвращает не весь JSON Gitea, а только краткий набор полей —
иначе ответ раздувается из-за permissions/url/настроек и обрезается в чате. иначе ответ раздувается из-за permissions/url/настроек и обрезается в чате.
@@ -102,8 +106,6 @@ class Tools:
params={"limit": safe_limit, "page": safe_page, "sort": safe_sort}, params={"limit": safe_limit, "page": safe_page, "sort": safe_sort},
) )
# Если Gitea вернула ошибку — _request отдаёт строку "Ошибка ...".
# Парсить как JSON нечего — возвращаем как есть.
if not raw or not raw.lstrip().startswith("["): if not raw or not raw.lstrip().startswith("["):
return raw return raw
@@ -115,7 +117,6 @@ class Tools:
if not isinstance(repos, list): if not isinstance(repos, list):
return raw return raw
# Пустой fields — режим «как раньше», отдаём весь JSON.
if not fields: if not fields:
return raw return raw
@@ -130,33 +131,33 @@ class Tools:
if len(repos) >= safe_limit: if len(repos) >= safe_limit:
hint = ( hint = (
f"\n\n[Есть ещё репозитории — вызови " f"\n\n[Есть ещё репозитории — вызови "
f"list_repositories(limit={safe_limit}, page={safe_page + 1})]" f"list_repositories_proxmox(limit={safe_limit}, page={safe_page + 1})]"
) )
return json.dumps(compact, ensure_ascii=False, indent=2) + hint return json.dumps(compact, ensure_ascii=False, indent=2) + hint
def get_repository_info(self, owner: str, repo: str) -> str: def get_repository_info_proxmox(self, owner: str, repo: str) -> str:
"""Получить информацию о репозитории.""" """Получить информацию о репозитории на Gitea на PROXMOX (192.168.31.57)."""
return self._request("GET", f"/repos/{quote(owner, safe='')}/{quote(repo, safe='')}") return self._request("GET", f"/repos/{quote(owner, safe='')}/{quote(repo, safe='')}")
def delete_repository(self, owner: str, repo: str) -> str: def delete_repository_proxmox(self, owner: str, repo: str) -> str:
"""Удалить репозиторий. Операция необратима.""" """Удалить репозиторий на Gitea на PROXMOX (192.168.31.57). Операция необратима."""
return self._request("DELETE", f"/repos/{quote(owner, safe='')}/{quote(repo, safe='')}") return self._request("DELETE", f"/repos/{quote(owner, safe='')}/{quote(repo, safe='')}")
def create_organization(self, org_name: str, description: str = "", visibility: str = "public") -> str: def create_organization_proxmox(self, org_name: str, description: str = "", visibility: str = "public") -> str:
"""Создать организацию.""" """Создать организацию на Gitea на PROXMOX (192.168.31.57)."""
return self._request("POST", "/orgs", json={"username": org_name, "description": description, "visibility": visibility}) return self._request("POST", "/orgs", json={"username": org_name, "description": description, "visibility": visibility})
def list_organizations(self) -> str: def list_organizations_proxmox(self) -> str:
"""Получить организации текущего пользователя.""" """Получить организации текущего пользователя на Gitea на PROXMOX (192.168.31.57)."""
return self._request("GET", "/user/orgs") return self._request("GET", "/user/orgs")
def list_files(self, owner: str, repo: str, path: str = "", branch: str = "") -> str: def list_files_proxmox(self, owner: str, repo: str, path: str = "", branch: str = "") -> str:
"""Получить список файлов и папок.""" """Получить список файлов и папок на Gitea на PROXMOX (192.168.31.57)."""
params = {"ref": branch} if branch else {} params = {"ref": branch} if branch else {}
return self._request("GET", f"/repos/{quote(owner, safe='')}/{quote(repo, safe='')}/contents/{quote(path, safe='/')}", params=params) return self._request("GET", f"/repos/{quote(owner, safe='')}/{quote(repo, safe='')}/contents/{quote(path, safe='/')}", params=params)
def get_file_content(self, owner: str, repo: str, file_path: str, branch: str = "") -> str: def get_file_content_proxmox(self, owner: str, repo: str, file_path: str, branch: str = "") -> str:
"""Получить содержимое файла и его SHA.""" """Получить содержимое файла и его SHA на Gitea на PROXMOX (192.168.31.57)."""
params = {"ref": branch} if branch else {} params = {"ref": branch} if branch else {}
result = self._request("GET", f"/repos/{quote(owner, safe='')}/{quote(repo, safe='')}/contents/{quote(file_path, safe='/')}", params=params) result = self._request("GET", f"/repos/{quote(owner, safe='')}/{quote(repo, safe='')}/contents/{quote(file_path, safe='/')}", params=params)
try: try:
@@ -166,33 +167,33 @@ class Tools:
except (ValueError, KeyError, TypeError): except (ValueError, KeyError, TypeError):
return result return result
def create_file(self, owner: str, repo: str, file_path: str, content: str, commit_message: str = "Добавлен файл", branch: str = "") -> str: def create_file_proxmox(self, owner: str, repo: str, file_path: str, content: str, commit_message: str = "Добавлен файл", branch: str = "") -> str:
"""Создать файл в репозитории.""" """Создать файл в репозитории на Gitea на PROXMOX (192.168.31.57)."""
payload = {"content": base64.b64encode(content.encode()).decode(), "message": commit_message} payload = {"content": base64.b64encode(content.encode()).decode(), "message": commit_message}
if branch: if branch:
payload["branch"] = branch payload["branch"] = branch
return self._request("POST", f"/repos/{quote(owner, safe='')}/{quote(repo, safe='')}/contents/{quote(file_path, safe='/')}", json=payload) return self._request("POST", f"/repos/{quote(owner, safe='')}/{quote(repo, safe='')}/contents/{quote(file_path, safe='/')}", json=payload)
def update_file(self, owner: str, repo: str, file_path: str, content: str, sha: str, commit_message: str = "Обновление файла", branch: str = "") -> str: def update_file_proxmox(self, owner: str, repo: str, file_path: str, content: str, sha: str, commit_message: str = "Обновление файла", branch: str = "") -> str:
"""Обновить существующий файл по SHA.""" """Обновить существующий файл по SHA на Gitea на PROXMOX (192.168.31.57)."""
payload = {"content": base64.b64encode(content.encode()).decode(), "message": commit_message, "sha": sha} payload = {"content": base64.b64encode(content.encode()).decode(), "message": commit_message, "sha": sha}
if branch: if branch:
payload["branch"] = branch payload["branch"] = branch
return self._request("PUT", f"/repos/{quote(owner, safe='')}/{quote(repo, safe='')}/contents/{quote(file_path, safe='/')}", json=payload) return self._request("PUT", f"/repos/{quote(owner, safe='')}/{quote(repo, safe='')}/contents/{quote(file_path, safe='/')}", json=payload)
def delete_file(self, owner: str, repo: str, file_path: str, sha: str, commit_message: str = "Удаление файла", branch: str = "") -> str: def delete_file_proxmox(self, owner: str, repo: str, file_path: str, sha: str, commit_message: str = "Удаление файла", branch: str = "") -> str:
"""Удалить файл по SHA.""" """Удалить файл по SHA на Gitea на PROXMOX (192.168.31.57)."""
payload = {"message": commit_message, "sha": sha} payload = {"message": commit_message, "sha": sha}
if branch: if branch:
payload["branch"] = branch payload["branch"] = branch
return self._request("DELETE", f"/repos/{quote(owner, safe='')}/{quote(repo, safe='')}/contents/{quote(file_path, safe='/')}", json=payload) return self._request("DELETE", f"/repos/{quote(owner, safe='')}/{quote(repo, safe='')}/contents/{quote(file_path, safe='/')}", json=payload)
def list_branches(self, owner: str, repo: str) -> str: def list_branches_proxmox(self, owner: str, repo: str) -> str:
"""Получить список веток репозитория.""" """Получить список веток репозитория на Gitea на PROXMOX (192.168.31.57)."""
return self._request("GET", f"/repos/{quote(owner, safe='')}/{quote(repo, safe='')}/branches") return self._request("GET", f"/repos/{quote(owner, safe='')}/{quote(repo, safe='')}/branches")
def download_repository_file_to_workspace(self, owner: str, repo: str, file_path: str, destination_name: str = "", branch: str = "", overwrite: bool = False) -> str: def download_repository_file_to_workspace_proxmox(self, owner: str, repo: str, file_path: str, destination_name: str = "", branch: str = "", overwrite: bool = False) -> str:
"""Скачать файл из Gitea (Proxmox) в локальную рабочую директорию Open WebUI.""" """Скачать файл из Gitea на PROXMOX (192.168.31.57) в локальную рабочую директорию Open WebUI."""
error = self._check() error = self._check()
if error: if error:
return error return error
@@ -231,10 +232,10 @@ class Tools:
temporary.replace(destination) temporary.replace(destination)
return json.dumps({"status": "ok", "path": str(destination), "size": len(raw)}, ensure_ascii=False, indent=2) return json.dumps({"status": "ok", "path": str(destination), "size": len(raw)}, ensure_ascii=False, indent=2)
except (OSError, ValueError, KeyError, requests.RequestException) as exc: except (OSError, ValueError, KeyError, requests.RequestException) as exc:
return f"Ошибка загрузки файла из Gitea (Proxmox): {exc}" return f"Ошибка загрузки файла из Gitea на Proxmox: {exc}"
def extract_zip(self, zip_path: str, destination_dir: str = "", overwrite: bool = False) -> str: def extract_zip_proxmox(self, zip_path: str, destination_dir: str = "", overwrite: bool = False) -> str:
"""Безопасно распаковать ZIP в рабочую директорию.""" """Безопасно распаковать ZIP в рабочую директорию инструмента Gitea на PROXMOX (192.168.31.57)."""
try: try:
archive = Path(zip_path).expanduser().resolve(strict=True) archive = Path(zip_path).expanduser().resolve(strict=True)
if not archive.is_file(): if not archive.is_file():