Add Gitea repo sync for credentials (admtracksteel/Keys)
This commit is contained in:
@@ -6,8 +6,10 @@
|
||||
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import configparser
|
||||
import time
|
||||
import requests
|
||||
from typing import Optional, Dict
|
||||
|
||||
# ============================================================
|
||||
@@ -17,6 +19,63 @@ from typing import Optional, Dict
|
||||
SEGREDOS_PATH = "/data/segredos.md"
|
||||
BOTVPS_HOST_PATH = "/app"
|
||||
|
||||
# ============================================================
|
||||
# GITEA REPO CREDENTIALS (FONTE PRINCIPAL)
|
||||
# ============================================================
|
||||
|
||||
GITEA_CREDS_REPO = "admtracksteel/Keys"
|
||||
GITEA_CREDS_FILE = "credentials.json"
|
||||
_gitea_creds_cache: Dict[str, str] = {}
|
||||
_gitea_creds_cache_time: float = 0
|
||||
|
||||
def get_gitea_creds_url() -> str:
|
||||
"""Retorna URL da API do Gitea."""
|
||||
return "https://git.reifonas.cloud/api/v1"
|
||||
|
||||
def fetch_from_gitea_repo(force: bool = False) -> Dict[str, Dict[str, str]]:
|
||||
"""
|
||||
Busca credenciais do repo Gitea admtracksteel/Keys.
|
||||
Faz cache com TTL de 5 minutos.
|
||||
"""
|
||||
global _gitea_creds_cache, _gitea_creds_cache_time
|
||||
|
||||
# Verifica cache
|
||||
if not force and time.time() - _gitea_creds_cache_time < CACHE_TTL and _gitea_creds_cache:
|
||||
return _gitea_creds_cache
|
||||
|
||||
try:
|
||||
# Obtém token do Gitea
|
||||
from credential_manager import gitea_token
|
||||
token = gitea_token()
|
||||
|
||||
# Busca arquivo no repo
|
||||
url = f"{get_gitea_creds_url()}/repos/admtracksteel/Keys/contents/{GITEA_CREDS_FILE}"
|
||||
headers = {"Authorization": f"token {token}"} if token else {}
|
||||
|
||||
response = requests.get(url, headers=headers, timeout=30)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
# Conteúdo está em base64
|
||||
import base64
|
||||
content_b64 = data.get("content", "").replace("\n", "")
|
||||
content = base64.b64decode(content_b64).decode("utf-8")
|
||||
_gitea_creds_cache = json.loads(content)
|
||||
_gitea_creds_cache_time = time.time()
|
||||
print(f"[CREDMAN] Credenciais carregadas do repo Gitea ({len(_gitea_creds_cache)} serviços)")
|
||||
return _gitea_creds_cache
|
||||
else:
|
||||
print(f"[CREDMAN] Erro ao buscar repo Gitea: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"[CREDMAN] Erro ao fetch_from_gitea_repo: {e}")
|
||||
|
||||
return _gitea_creds_cache if _gitea_creds_cache else {}
|
||||
|
||||
def get_gitea_cred(service: str, key: str, force: bool = False) -> Optional[str]:
|
||||
"""Busca credencial específica do repo Gitea."""
|
||||
creds = fetch_from_gitea_repo(force)
|
||||
return creds.get(service, {}).get(key)
|
||||
|
||||
# ============================================================
|
||||
# FONTES DE CREDENCIAIS
|
||||
# ============================================================
|
||||
@@ -297,6 +356,8 @@ def reload_credential(service: str, key: str) -> Optional[str]:
|
||||
|
||||
def gitea_token() -> str:
|
||||
"""Retorna token de acesso do Gitea."""
|
||||
token = get_gitea_cred("gitea", "TOKEN")
|
||||
if not token:
|
||||
token = get_credential("gitea", "INSTALL_LOCK")
|
||||
if not token:
|
||||
token = get_credential("gitea", "TOKEN")
|
||||
@@ -318,6 +379,8 @@ def supabase_url() -> str:
|
||||
|
||||
def supabase_anon_key() -> str:
|
||||
"""Retorna ANON_KEY do Supabase."""
|
||||
key = get_gitea_cred("supabase", "ANON_KEY")
|
||||
if not key:
|
||||
key = get_credential("supabase", "ANON_KEY")
|
||||
if not key:
|
||||
key = get_segredo("supabase", "ANON_KEY")
|
||||
@@ -325,6 +388,8 @@ def supabase_anon_key() -> str:
|
||||
|
||||
def supabase_service_role_key() -> str:
|
||||
"""Retorna SERVICE_ROLE_KEY do Supabase."""
|
||||
key = get_gitea_cred("supabase", "SERVICE_ROLE_KEY")
|
||||
if not key:
|
||||
key = get_credential("supabase", "SERVICE_ROLE_KEY")
|
||||
if not key:
|
||||
key = get_segredo("supabase", "SERVICE_ROLE_KEY")
|
||||
@@ -332,6 +397,8 @@ def supabase_service_role_key() -> str:
|
||||
|
||||
def supabase_jwt_secret() -> str:
|
||||
"""Retorna JWT_SECRET do Supabase."""
|
||||
secret = get_gitea_cred("supabase", "JWT_SECRET")
|
||||
if not secret:
|
||||
secret = get_credential("supabase", "JWT_SECRET")
|
||||
if not secret:
|
||||
secret = get_segredo("supabase", "JWT_SECRET")
|
||||
@@ -339,6 +406,8 @@ def supabase_jwt_secret() -> str:
|
||||
|
||||
def coolify_app_key() -> str:
|
||||
"""Retorna APP_KEY do Coolify."""
|
||||
key = get_gitea_cred("coolify", "APP_KEY")
|
||||
if not key:
|
||||
key = get_credential("coolify", "APP_KEY")
|
||||
if not key:
|
||||
key = get_segredo("coolify", "APP_KEY")
|
||||
@@ -442,6 +511,37 @@ def sync_credentials() -> dict:
|
||||
|
||||
return result
|
||||
|
||||
# ============================================================
|
||||
# GITEA REPO SYNC
|
||||
# ============================================================
|
||||
|
||||
def sync_from_gitea_repo(force: bool = False) -> dict:
|
||||
"""
|
||||
Força sincronização do repo Gitea admtracksteel/Keys.
|
||||
Retorna status do sync.
|
||||
"""
|
||||
global _gitea_creds_cache, _gitea_creds_cache_time
|
||||
|
||||
clear_cache()
|
||||
_gitea_creds_cache_time = 0
|
||||
|
||||
creds = fetch_from_gitea_repo(force=force)
|
||||
|
||||
services = list(creds.keys())
|
||||
|
||||
return {
|
||||
"status": "synced" if creds else "failed",
|
||||
"repo": GITEA_CREDS_REPO,
|
||||
"file": GITEA_CREDS_FILE,
|
||||
"services_count": len(creds),
|
||||
"services": services,
|
||||
"timestamp": time.time()
|
||||
}
|
||||
|
||||
def get_gitea_repo_credentials() -> Dict[str, Dict[str, str]]:
|
||||
"""Retorna todas as credenciais do repo Gitea."""
|
||||
return fetch_from_gitea_repo()
|
||||
|
||||
# ============================================================
|
||||
# STATUS
|
||||
# ============================================================
|
||||
@@ -450,6 +550,7 @@ def get_services_status() -> dict:
|
||||
"""Retorna status de todos os serviços."""
|
||||
status = {}
|
||||
segredos = get_segredos()
|
||||
gitea_creds = get_gitea_repo_credentials()
|
||||
|
||||
for service_id, source in CREDENTIAL_SOURCES.items():
|
||||
path = source["path"]
|
||||
@@ -461,17 +562,27 @@ def get_services_status() -> dict:
|
||||
keys_count = len(creds)
|
||||
|
||||
segredos_keys = len(segredos.get(service_id, {}))
|
||||
from_segredos = segredos_keys > 0
|
||||
gitea_keys = len(gitea_creds.get(service_id, {}))
|
||||
|
||||
status[service_id] = {
|
||||
"description": source["description"],
|
||||
"path": path,
|
||||
"exists": exists,
|
||||
"keys_count": keys_count,
|
||||
"from_segredos": from_segredos,
|
||||
"from_gitea_repo": gitea_keys > 0,
|
||||
"gitea_keys": gitea_keys,
|
||||
"from_segredos": segredos_keys > 0,
|
||||
"segredos_keys": segredos_keys
|
||||
}
|
||||
|
||||
status["gitea_repo"] = {
|
||||
"description": "Repo Git (admtracksteel/Keys)",
|
||||
"repo": GITEA_CREDS_REPO,
|
||||
"file": GITEA_CREDS_FILE,
|
||||
"available": len(gitea_creds) > 0,
|
||||
"services_count": len(gitea_creds)
|
||||
}
|
||||
|
||||
return status
|
||||
|
||||
# ============================================================
|
||||
|
||||
33
main.py
33
main.py
@@ -12,7 +12,7 @@ import audio_handler
|
||||
|
||||
from ai_agent import query_agent
|
||||
from config import get_config, save_config
|
||||
from credential_manager import sync_credentials
|
||||
from credential_manager import sync_credentials, sync_from_gitea_repo
|
||||
|
||||
# Carrega as variáveis do .env
|
||||
load_dotenv()
|
||||
@@ -25,17 +25,20 @@ templates = Jinja2Templates(directory="templates")
|
||||
# ============================================================
|
||||
# AUTO-SYNC DE CREDENCIAIS NO STARTUP
|
||||
# ============================================================
|
||||
print("[INIT] Sincronizando credenciais...")
|
||||
print("[INIT] Sincronizando credenciais do repo Gitea...")
|
||||
sync_result = sync_from_gitea_repo()
|
||||
print(f"[INIT] Repo Gitea: {sync_result['status']} ({sync_result['services_count']} serviços)")
|
||||
print("[INIT] Sincronizando fallback local...")
|
||||
sync_result = sync_credentials()
|
||||
print(f"[INIT] Credenciais sincronizadas: {sync_result['status']}")
|
||||
print(f"[INIT] Services: {', '.join(sync_result['services'].keys())}")
|
||||
print(f"[INIT] Local: {sync_result['status']}")
|
||||
|
||||
# ============================================================
|
||||
# EVENTO DE STARTUP
|
||||
# ============================================================
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
print("[STARTUP] Sincronizando credenciais...")
|
||||
print("[STARTUP] Sincronizando credenciais do repo Gitea...")
|
||||
sync_from_gitea_repo()
|
||||
sync_credentials()
|
||||
print("[STARTUP] Credenciais sincronizadas com sucesso!")
|
||||
|
||||
@@ -298,10 +301,28 @@ async def list_llm_models(is_auth: bool = Depends(verify_password)):
|
||||
|
||||
@app.post("/api/sync-credentials")
|
||||
async def sync_creds(is_auth: bool = Depends(verify_password)):
|
||||
"""Força sincronização de credenciais."""
|
||||
"""Força sincronização de credenciais (fallback local)."""
|
||||
result = sync_credentials()
|
||||
return JSONResponse(content=result)
|
||||
|
||||
@app.post("/api/sync-from-repo")
|
||||
async def sync_from_repo(is_auth: bool = Depends(verify_password)):
|
||||
"""Força sincronização do repo Gitea admtracksteel/Keys."""
|
||||
from credential_manager import get_gitea_repo_credentials
|
||||
result = sync_from_gitea_repo(force=True)
|
||||
return JSONResponse(content=result)
|
||||
|
||||
@app.get("/api/credentials-repo")
|
||||
async def get_repo_credentials(is_auth: bool = Depends(verify_password)):
|
||||
"""Retorna credenciais do repo Gitea."""
|
||||
from credential_manager import get_gitea_repo_credentials
|
||||
creds = get_gitea_repo_credentials()
|
||||
return JSONResponse(content={
|
||||
"repo": "admtracksteel/Keys",
|
||||
"services": creds,
|
||||
"count": len(creds)
|
||||
})
|
||||
|
||||
@app.get("/api/tools")
|
||||
async def list_tools(is_auth: bool = Depends(verify_password)):
|
||||
"""Lista todas as ferramentas disponíveis."""
|
||||
|
||||
@@ -800,10 +800,17 @@
|
||||
<div class="card">
|
||||
<div style="margin-bottom: 1rem; padding: 0.75rem; background: var(--bg-input); border-radius: 8px; font-size: 0.85rem;">
|
||||
<strong>Status:</strong> <span id="orchestrator-status">Carregando...</span>
|
||||
<button type="button" class="btn" style="margin-left: 1rem; padding: 0.4rem 0.75rem; font-size: 0.75rem;" onclick="syncCredentials()">
|
||||
<button type="button" class="btn" style="margin-left: 0.5rem; padding: 0.4rem 0.75rem; font-size: 0.75rem;" onclick="syncFromRepo()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="14" height="14"><path d="M23 4v6h-6M1 20v-6h6"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>
|
||||
Sync Credenciais
|
||||
Sync Repo
|
||||
</button>
|
||||
<button type="button" class="btn" style="margin-left: 0.5rem; padding: 0.4rem 0.75rem; font-size: 0.75rem;" onclick="syncCredentials()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="14" height="14"><path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
||||
Sync Local
|
||||
</button>
|
||||
</div>
|
||||
<div style="margin-bottom: 1rem; padding: 0.5rem 0.75rem; background: var(--success); opacity: 0.15; border-radius: 6px; font-size: 0.75rem;">
|
||||
<strong>Repo Gitea:</strong> <span id="repo-status">Carregando...</span>
|
||||
</div>
|
||||
|
||||
<div class="config-grid">
|
||||
@@ -1220,12 +1227,40 @@
|
||||
if (statusEl) {
|
||||
statusEl.innerHTML = '<strong>Planner:</strong> ' + data.planner.name + ' | <strong>Executor:</strong> ' + data.executor.name + ' | <strong>Ferramentas:</strong> ' + data.available_tools;
|
||||
}
|
||||
|
||||
// Repo status
|
||||
const repoEl = document.getElementById('repo-status');
|
||||
if (repoEl) {
|
||||
const creds = data.credentials || {};
|
||||
const giteaRepo = creds.gitea_repo || {};
|
||||
if (giteaRepo.available) {
|
||||
repoEl.innerHTML = '<span style="color: var(--success);">✅ Online</span> - ' + giteaRepo.services_count + ' serviço(s) disponível(is)';
|
||||
} else {
|
||||
repoEl.innerHTML = '<span style="color: var(--text-muted);">⏳ Sincronizando...</span>';
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
const statusEl = document.getElementById('orchestrator-status');
|
||||
if (statusEl) statusEl.textContent = 'Erro ao carregar';
|
||||
}
|
||||
}
|
||||
|
||||
async function syncFromRepo() {
|
||||
try {
|
||||
const res = await apiFetch('/api/sync-from-repo', { method: 'POST' });
|
||||
const data = await res.json();
|
||||
if (data.status === 'synced') {
|
||||
showToast('Repo sincronizado! ' + data.services_count + ' serviços carregados');
|
||||
} else {
|
||||
showToast('Erro ao sincronizar repo: ' + data.status, true);
|
||||
}
|
||||
loadOrchestratorStatus();
|
||||
loadCredentials();
|
||||
} catch (e) {
|
||||
showToast('Erro ao sincronizar repo.', true);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadLLMModels() {
|
||||
try {
|
||||
const res = await apiFetch('/api/llm-models');
|
||||
|
||||
Reference in New Issue
Block a user