diff --git a/gitea_manager_proxmox.py b/gitea_manager_proxmox.py index 8750bae..86aad48 100644 --- a/gitea_manager_proxmox.py +++ b/gitea_manager_proxmox.py @@ -1,8 +1,8 @@ """ -title: Gitea Manager — Proxmox +title: Gitea Manager (Proxmox) author: Claude -version: 1.3.0 -description: Инструмент для управления второй Gitea (Proxmox, 192.168.31.57:3000) из Open WebUI — создание и удаление репозиториев, организаций, файлов, веток, загрузка и безопасная распаковка ZIP-архивов. +version: 1.4.0 +description: Инструмент для управления Gitea на Proxmox (192.168.31.57:3000) из Open WebUI — создание и удаление репозиториев, организаций, файлов, веток, загрузка и безопасная распаковка ZIP-архивов. Все методы имеют суффикс _proxmox, чтобы не путаться с инструментом для основной Gitea. requirements: requests """ import base64 @@ -20,13 +20,17 @@ from pydantic import BaseModel, Field 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): - GITEA_URL: str = Field(default="http://192.168.31.57:3000", description="Базовый URL Gitea (Proxmox) без слэша в конце.") - GITEA_TOKEN: str = Field(default="", description="API-токен Gitea. Заполните в настройках инструмента.") + GITEA_URL: str = Field(default="http://192.168.31.57:3000", description="Базовый URL Gitea на Proxmox без слэша в конце.") + GITEA_TOKEN: str = Field(default="", description="API-токен Gitea на Proxmox. Заполните в настройках инструмента.") 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="Корневая рабочая директория.") MAX_ARCHIVE_FILES: int = Field(default=10000, description="Максимальное количество элементов ZIP.") MAX_ARCHIVE_SIZE_MB: int = Field(default=2048, description="Максимальный распакованный размер ZIP в МБ.") @@ -35,15 +39,15 @@ class Tools: self.valves = self.Valves() def _check(self) -> Optional[str]: - """Проверить доступность инструмента и наличие токена.""" + """Проверить доступность инструмента и наличие токена (Gitea на Proxmox).""" if not self.valves.ENABLED: - return "Инструмент Gitea Manager — Proxmox выключен." + return "Инструмент Gitea Manager (Proxmox) выключен." if not self.valves.GITEA_TOKEN: - return "API-токен Gitea не задан в настройках инструмента." + return "API-токен Gitea на Proxmox не задан в настройках инструмента." return None def _request(self, method: str, path: str, **kwargs) -> str: - """Выполнить запрос к Gitea (Proxmox) и вернуть JSON или сообщение об ошибке.""" + """Выполнить запрос к Gitea на Proxmox (192.168.31.57) и вернуть JSON или сообщение об ошибке.""" error = self._check() if error: return error @@ -60,17 +64,17 @@ class Tools: return f"Ошибка {response.status_code}: {response.text}" return response.text or "Операция выполнена успешно." 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}) - 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}) - def list_repositories( + def list_repositories_proxmox( self, limit: int = 50, page: int = 1, @@ -78,7 +82,7 @@ class Tools: fields: Optional[str] = "name,private,language,html_url,description,updated_at,default_branch", ) -> str: """ - Получить список репозиториев текущего пользователя в компактном виде. + Получить список репозиториев текущего пользователя на Gitea на PROXMOX (192.168.31.57) в компактном виде. По умолчанию возвращает не весь JSON Gitea, а только краткий набор полей — иначе ответ раздувается из-за permissions/url/настроек и обрезается в чате. @@ -102,8 +106,6 @@ class Tools: params={"limit": safe_limit, "page": safe_page, "sort": safe_sort}, ) - # Если Gitea вернула ошибку — _request отдаёт строку "Ошибка ...". - # Парсить как JSON нечего — возвращаем как есть. if not raw or not raw.lstrip().startswith("["): return raw @@ -115,7 +117,6 @@ class Tools: if not isinstance(repos, list): return raw - # Пустой fields — режим «как раньше», отдаём весь JSON. if not fields: return raw @@ -130,33 +131,33 @@ class Tools: if len(repos) >= safe_limit: hint = ( 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 - 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='')}") - 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='')}") - 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}) - def list_organizations(self) -> str: - """Получить организации текущего пользователя.""" + def list_organizations_proxmox(self) -> str: + """Получить организации текущего пользователя на Gitea на PROXMOX (192.168.31.57).""" 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 {} 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: - """Получить содержимое файла и его SHA.""" + def get_file_content_proxmox(self, owner: str, repo: str, file_path: str, branch: str = "") -> str: + """Получить содержимое файла и его SHA на Gitea на PROXMOX (192.168.31.57).""" 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) try: @@ -166,33 +167,33 @@ class Tools: except (ValueError, KeyError, TypeError): 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} if branch: payload["branch"] = branch 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: - """Обновить существующий файл по SHA.""" + def update_file_proxmox(self, owner: str, repo: str, file_path: str, content: str, sha: str, commit_message: str = "Обновление файла", branch: str = "") -> str: + """Обновить существующий файл по SHA на Gitea на PROXMOX (192.168.31.57).""" payload = {"content": base64.b64encode(content.encode()).decode(), "message": commit_message, "sha": sha} if branch: payload["branch"] = branch 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: - """Удалить файл по SHA.""" + def delete_file_proxmox(self, owner: str, repo: str, file_path: str, sha: str, commit_message: str = "Удаление файла", branch: str = "") -> str: + """Удалить файл по SHA на Gitea на PROXMOX (192.168.31.57).""" payload = {"message": commit_message, "sha": sha} if branch: payload["branch"] = branch 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") - def download_repository_file_to_workspace(self, owner: str, repo: str, file_path: str, destination_name: str = "", branch: str = "", overwrite: bool = False) -> str: - """Скачать файл из Gitea (Proxmox) в локальную рабочую директорию Open WebUI.""" + 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 (192.168.31.57) в локальную рабочую директорию Open WebUI.""" error = self._check() if error: return error @@ -231,10 +232,10 @@ class Tools: temporary.replace(destination) return json.dumps({"status": "ok", "path": str(destination), "size": len(raw)}, ensure_ascii=False, indent=2) 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: - """Безопасно распаковать ZIP в рабочую директорию.""" + def extract_zip_proxmox(self, zip_path: str, destination_dir: str = "", overwrite: bool = False) -> str: + """Безопасно распаковать ZIP в рабочую директорию инструмента Gitea на PROXMOX (192.168.31.57).""" try: archive = Path(zip_path).expanduser().resolve(strict=True) if not archive.is_file():