Add ERP Local Agent secure self update framework

This commit is contained in:
A R R R Associates
2026-08-09 12:45:44 +05:30
parent b374aadf44
commit f79862203b
25 changed files with 1011 additions and 583 deletions
+33 -4
View File
@@ -1,4 +1,4 @@
from __future__ import annotations
from __future__ import annotations
from datetime import date, datetime, timezone
import asyncio
@@ -80,7 +80,7 @@ from app.modules.documents.services import (
from app.modules.services.models import ClientServiceSubscription
from app.modules.core.tenancy.models import Branch, Tenant
from app.modules.core.tenancy.year_control import is_row_financial_year_locked
from app.modules.documents.agent_package import build_agent_env, build_preconfigured_agent_zip
from app.modules.documents.agent_package import ERP_LOCAL_AGENT_VERSION, build_agent_env, build_preconfigured_agent_zip, build_agent_update_zip
from app.modules.documents.models import BranchStorageNode
from app.modules.services.task_documents import (
@@ -1073,7 +1073,7 @@ def _default_node_name(db, tenant_id: int, branch_id: int | None) -> str:
def _agent_download_filename(node_code: str, suffix: str) -> str:
safe = re.sub(r"[^A-Za-z0-9_.-]+", "_", node_code or "storage_node")
return f"AuditFirmStorageAgent_{safe}{suffix}"
return f"ERPLocalAgent_{safe}{suffix}"
def _find_existing_storage_node(db, tenant_id: int, branch_id: int | None) -> BranchStorageNode | None:
@@ -1226,7 +1226,7 @@ def _branch_name_map(db, branches=None):
def _storage_scope_title(user, scope) -> str:
if scope.is_system_admin:
return "All audit firms monitoring only"
return "All audit firms — monitoring only"
if scope.is_firm_admin:
return "All branches of your audit firm"
if _is_partner_branch_storage_scope(scope):
@@ -1282,6 +1282,7 @@ def storage_nodes(request: Request):
recent_download_requests=requests,
generated_secret=None,
storage_scope_title=_storage_scope_title(user, scope),
erp_local_agent_version=ERP_LOCAL_AGENT_VERSION,
can_manage_branch_storage=_can_manage_branch_storage(scope),
forced_branch_id=branch_filter,
)
@@ -1575,6 +1576,33 @@ def _agent_auth(db, request: Request, x_node_code: str | None, x_node_secret: st
return node, None
@router.get("/erp-local-agent/update-manifest")
def erp_local_agent_update_manifest(request: Request, x_node_code: str | None = Header(None), x_node_secret: str | None = Header(None)):
import hashlib
db = CommonSessionLocal()
try:
node, error = _agent_auth(db, request, x_node_code, x_node_secret)
if error:
return error
package = build_agent_update_zip()
db.commit()
return {"ok": True, "agent_name": "ERP Local Agent", "latest_version": ERP_LOCAL_AGENT_VERSION, "sha256": hashlib.sha256(package).hexdigest(), "size_bytes": len(package), "download_url": "/documents/erp-local-agent/update-package", "channel": "stable"}
finally:
db.close()
@router.get("/erp-local-agent/update-package")
def erp_local_agent_update_package(request: Request, x_node_code: str | None = Header(None), x_node_secret: str | None = Header(None)):
db = CommonSessionLocal()
try:
node, error = _agent_auth(db, request, x_node_code, x_node_secret)
if error:
return error
package = build_agent_update_zip()
db.commit()
filename = f"ERP_Local_Agent_{ERP_LOCAL_AGENT_VERSION}.zip"
return Response(package, media_type="application/zip", headers={"Content-Disposition": f'attachment; filename="{filename}"'})
finally:
db.close()
@router.post("/storage-agent/heartbeat")
async def storage_agent_heartbeat(request: Request, x_node_code: str | None = Header(None), x_node_secret: str | None = Header(None)):
db = CommonSessionLocal()
@@ -1891,3 +1919,4 @@ def delete_document(request: Request, document_id: int, csrf_token: str = Form(.
return RedirectResponse(url=f"/documents/engagements/{document.engagement_id}?deleted=1", status_code=303)
finally:
db.close()