Initial curated Unraid-AppOps-Gateway source from 3bd6f6fee310125bfbb4d4650389a817f498724d
This commit is contained in:
commit
1dfbca08a1
528 files changed
+131054
No files matched your search
@@ -0,0 +1,327 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from ..db import session as db_session
|
||||
from ..db.models import Policy
|
||||
from .audit_logger import log_event
|
||||
from .gitea_project_auto_registration import GiteaProjectAutoRegistration
|
||||
from .inventory_discovery import run_discovery
|
||||
from .mcp_hub_deployment_reconciliation import McpHubDeploymentReconciliation
|
||||
from .media_codec_dashboard_deployment_reconciliation import (
|
||||
MediaCodecDashboardDeploymentReconciliation,
|
||||
)
|
||||
from .privatemuse_deployment_enrollment import PrivateMuseDeploymentEnrollment
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_MCP_HUB_APPLICATION_ID = "itworx-mcp-hub"
|
||||
_MCP_HUB_REPOSITORY = "jens/itworx_mcp_hub"
|
||||
_MCP_HUB_DEPLOYMENT_ACTIONS = ("deployment.apply", "deployment.rollback")
|
||||
|
||||
|
||||
def _reconcile_mcp_hub_exact_policies(session: Session) -> dict[str, object]:
|
||||
"""Create only the exact MCP Hub deployment policies after state proof."""
|
||||
|
||||
existing_by_action: dict[str, Policy | None] = {}
|
||||
for action in _MCP_HUB_DEPLOYMENT_ACTIONS:
|
||||
matches = session.exec(
|
||||
select(Policy).where(
|
||||
(Policy.action == action) & (Policy.target == _MCP_HUB_APPLICATION_ID)
|
||||
)
|
||||
).all()
|
||||
if len(matches) > 1:
|
||||
return {
|
||||
"status": "conflict",
|
||||
"action": action,
|
||||
"created": 0,
|
||||
"policyIds": [],
|
||||
}
|
||||
existing = matches[0] if matches else None
|
||||
if existing is not None and (
|
||||
existing.manual != "allowed"
|
||||
or existing.voice != "blocked"
|
||||
or existing.risk != "high"
|
||||
):
|
||||
return {
|
||||
"status": "conflict",
|
||||
"action": action,
|
||||
"created": 0,
|
||||
"policyIds": [],
|
||||
}
|
||||
existing_by_action[action] = existing
|
||||
|
||||
policy_ids: list[str] = []
|
||||
created = 0
|
||||
for action in _MCP_HUB_DEPLOYMENT_ACTIONS:
|
||||
existing = existing_by_action[action]
|
||||
if existing is not None:
|
||||
policy_ids.append(existing.id)
|
||||
continue
|
||||
suffix = action.rsplit(".", 1)[-1]
|
||||
policy_id = f"mcp-hub-reconciled-{suffix}"
|
||||
if session.get(Policy, policy_id) is not None:
|
||||
return {
|
||||
"status": "conflict",
|
||||
"action": action,
|
||||
"created": 0,
|
||||
"policyIds": [],
|
||||
}
|
||||
session.add(
|
||||
Policy(
|
||||
id=policy_id,
|
||||
action=action,
|
||||
target=_MCP_HUB_APPLICATION_ID,
|
||||
voice="blocked",
|
||||
manual="allowed",
|
||||
risk="high",
|
||||
reason=(
|
||||
"Exact MCP Hub deployment policy created only after "
|
||||
"fail-closed runtime state reconciliation."
|
||||
),
|
||||
provenance_json={
|
||||
"createdBy": "mcp-hub-state-reconciliation/v1",
|
||||
"repository": _MCP_HUB_REPOSITORY,
|
||||
"policyTemplateVersion": "exact-target/v1",
|
||||
},
|
||||
)
|
||||
)
|
||||
policy_ids.append(policy_id)
|
||||
created += 1
|
||||
session.commit()
|
||||
return {"status": "ready", "created": created, "policyIds": policy_ids}
|
||||
|
||||
|
||||
def _ensure_mcp_hub_exact_policies() -> dict[str, object]:
|
||||
with Session(db_session.engine) as session:
|
||||
return _reconcile_mcp_hub_exact_policies(session)
|
||||
|
||||
|
||||
def _audit_privatemuse_enrollment(enrollment: dict[str, object]) -> None:
|
||||
status = str(enrollment.get("status") or "error")
|
||||
failure_reason = str(enrollment.get("reason") or "")
|
||||
reason = "Bounded startup reconciliation for the existing PrivateMuse deployment target."
|
||||
if failure_reason:
|
||||
reason = f"{reason} Enrollment result: {status}; {failure_reason}"
|
||||
with Session(db_session.engine) as session:
|
||||
log_event(
|
||||
session,
|
||||
source="system",
|
||||
identity="appops-startup",
|
||||
action_id="deployment.target.enroll",
|
||||
target="jens/privatemuse",
|
||||
target_type="deployment-target",
|
||||
risk="high",
|
||||
policy_decision="allowed",
|
||||
reason=reason,
|
||||
result="success" if status == "enrolled" else "failed",
|
||||
after_state={
|
||||
"status": status,
|
||||
"repository": enrollment.get("repository", "jens/privatemuse"),
|
||||
"legacyRevision": enrollment.get("legacy_revision", ""),
|
||||
"composeProject": enrollment.get("compose_project", ""),
|
||||
"containers": enrollment.get("containers", []),
|
||||
},
|
||||
redacted_metadata={"failureReason": failure_reason} if failure_reason else None,
|
||||
)
|
||||
|
||||
|
||||
def _audit_mcp_hub_reconciliation(result: dict[str, object]) -> None:
|
||||
status = str(result.get("status") or "error")
|
||||
failure_reason = str(result.get("reason") or "")
|
||||
reason = "Fail-closed startup reconciliation for the verified MCP Hub deployment state."
|
||||
if failure_reason:
|
||||
reason = f"{reason} Reconciliation result: {status}; {failure_reason}"
|
||||
with Session(db_session.engine) as session:
|
||||
log_event(
|
||||
session,
|
||||
source="system",
|
||||
identity="appops-startup",
|
||||
action_id="deployment.state.reconcile",
|
||||
target="jens/itworx_mcp_hub",
|
||||
target_type="deployment-target",
|
||||
risk="high",
|
||||
policy_decision="allowed",
|
||||
reason=reason,
|
||||
result="success" if status == "reconciled" else "failed",
|
||||
after_state={
|
||||
"status": status,
|
||||
"repository": result.get("repository", "jens/itworx_mcp_hub"),
|
||||
"composeProject": result.get("composeProject", "itworx-mcp-hub"),
|
||||
"policyStatus": result.get("policyStatus", "not_checked"),
|
||||
"policyIds": result.get("policyIds", []),
|
||||
"policiesCreated": result.get("policiesCreated", 0),
|
||||
},
|
||||
redacted_metadata={"failureReason": failure_reason} if failure_reason else None,
|
||||
)
|
||||
|
||||
|
||||
def _audit_media_codec_dashboard_reconciliation(result: dict[str, object]) -> None:
|
||||
status = str(result.get("status") or "error")
|
||||
failure_reason = str(result.get("reason") or "")
|
||||
reason = "Fail-closed startup reconciliation for the verified Media Codec Dashboard deployment state."
|
||||
if failure_reason:
|
||||
reason = f"{reason} Reconciliation result: {status}; {failure_reason}"
|
||||
with Session(db_session.engine) as session:
|
||||
log_event(
|
||||
session,
|
||||
source="system",
|
||||
identity="appops-startup",
|
||||
action_id="deployment.state.reconcile",
|
||||
target="jens/media-codec-dashboard",
|
||||
target_type="deployment-target",
|
||||
risk="high",
|
||||
policy_decision="allowed",
|
||||
reason=reason,
|
||||
result="success" if status == "reconciled" else "failed",
|
||||
after_state={
|
||||
"status": status,
|
||||
"repository": result.get("repository", "jens/media-codec-dashboard"),
|
||||
"liveRevision": result.get("live_revision", ""),
|
||||
"previousRecordedRevision": result.get("previous_recorded_revision", ""),
|
||||
"container": result.get("container", ""),
|
||||
},
|
||||
redacted_metadata={"failureReason": failure_reason} if failure_reason else None,
|
||||
)
|
||||
|
||||
|
||||
def _sync_gitea_projects() -> dict[str, object]:
|
||||
with Session(db_session.engine) as session:
|
||||
result = GiteaProjectAutoRegistration().sync(session)
|
||||
summary = result.get("summary") if isinstance(result.get("summary"), dict) else {}
|
||||
changed = int(summary.get("changedThisSync", 0) or 0)
|
||||
if changed:
|
||||
log_event(
|
||||
session,
|
||||
source="system",
|
||||
identity="appops-project-sync",
|
||||
action_id="deployment.targets.reconcile",
|
||||
target="jens/*",
|
||||
target_type="deployment-project-catalog",
|
||||
risk="medium",
|
||||
policy_decision="allowed",
|
||||
reason="Automatic Gitea project registration reconciled changed owned repositories.",
|
||||
result="success" if result.get("status") == "ok" else "failed",
|
||||
after_state={
|
||||
"status": result.get("status"),
|
||||
"total": summary.get("total", 0),
|
||||
"registered": summary.get("registered", 0),
|
||||
"knownUnconfigured": summary.get("knownUnconfigured", 0),
|
||||
"conflicts": summary.get("conflicts", 0),
|
||||
"errors": summary.get("errors", 0),
|
||||
"changed": changed,
|
||||
},
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
async def _run_gitea_project_sync() -> None:
|
||||
try:
|
||||
result = await asyncio.to_thread(_sync_gitea_projects)
|
||||
summary = result.get("summary") if isinstance(result.get("summary"), dict) else {}
|
||||
if result.get("status") != "ok":
|
||||
logger.warning(
|
||||
"Gitea project registration sync degraded: status=%s reason=%s",
|
||||
result.get("status"),
|
||||
result.get("reason", "unspecified"),
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
"Gitea project registration sync complete: total=%s registered=%s changed=%s",
|
||||
summary.get("total", 0),
|
||||
summary.get("registered", 0),
|
||||
summary.get("changedThisSync", 0),
|
||||
)
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception: # noqa: BLE001 - catalog sync must never take down AppOps.
|
||||
logger.exception("Gitea project registration sync failed")
|
||||
|
||||
|
||||
async def run_periodic_discovery(interval_seconds: int) -> None:
|
||||
"""Reconcile deployment project catalog, bounded target state and inventory."""
|
||||
await _run_gitea_project_sync()
|
||||
|
||||
try:
|
||||
enrollment = await asyncio.to_thread(PrivateMuseDeploymentEnrollment().run)
|
||||
await asyncio.to_thread(_audit_privatemuse_enrollment, enrollment)
|
||||
if enrollment.get("status") != "enrolled":
|
||||
logger.warning(
|
||||
"PrivateMuse deployment enrollment did not complete: status=%s reason=%s",
|
||||
enrollment.get("status"),
|
||||
enrollment.get("reason", "unspecified"),
|
||||
)
|
||||
else:
|
||||
logger.info("PrivateMuse deployment target is enrolled")
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception: # noqa: BLE001 - enrollment failure must not take down AppOps.
|
||||
logger.exception("PrivateMuse deployment enrollment failed")
|
||||
|
||||
try:
|
||||
reconciliation = await asyncio.to_thread(McpHubDeploymentReconciliation().run)
|
||||
if reconciliation.get("status") == "reconciled":
|
||||
policy_result = await asyncio.to_thread(_ensure_mcp_hub_exact_policies)
|
||||
reconciliation = {
|
||||
**reconciliation,
|
||||
"policyStatus": policy_result.get("status", "error"),
|
||||
"policyIds": policy_result.get("policyIds", []),
|
||||
"policiesCreated": policy_result.get("created", 0),
|
||||
}
|
||||
if policy_result.get("status") != "ready":
|
||||
reconciliation = {
|
||||
**reconciliation,
|
||||
"status": "policy_conflict",
|
||||
"reason": "exact MCP Hub deployment policy reconciliation failed closed",
|
||||
}
|
||||
await asyncio.to_thread(_audit_mcp_hub_reconciliation, reconciliation)
|
||||
if reconciliation.get("status") != "reconciled":
|
||||
logger.warning(
|
||||
"MCP Hub deployment reconciliation did not complete: status=%s reason=%s",
|
||||
reconciliation.get("status"),
|
||||
reconciliation.get("reason", "unspecified"),
|
||||
)
|
||||
else:
|
||||
logger.info("MCP Hub deployment state is reconciled")
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception: # noqa: BLE001 - reconciliation failure must not take down AppOps.
|
||||
logger.exception("MCP Hub deployment reconciliation failed")
|
||||
|
||||
try:
|
||||
reconciliation = await asyncio.to_thread(
|
||||
MediaCodecDashboardDeploymentReconciliation().run
|
||||
)
|
||||
await asyncio.to_thread(
|
||||
_audit_media_codec_dashboard_reconciliation, reconciliation
|
||||
)
|
||||
if reconciliation.get("status") != "reconciled":
|
||||
logger.warning(
|
||||
"Media Codec Dashboard deployment reconciliation did not complete: status=%s reason=%s",
|
||||
reconciliation.get("status"),
|
||||
reconciliation.get("reason", "unspecified"),
|
||||
)
|
||||
else:
|
||||
logger.info("Media Codec Dashboard deployment state is reconciled")
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception: # noqa: BLE001 - reconciliation failure must not take down AppOps.
|
||||
logger.exception("Media Codec Dashboard deployment reconciliation failed")
|
||||
|
||||
if interval_seconds <= 0:
|
||||
return
|
||||
|
||||
await asyncio.sleep(min(10, interval_seconds))
|
||||
while True:
|
||||
try:
|
||||
await asyncio.to_thread(run_discovery)
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception: # noqa: BLE001 - discovery must never take down the gateway.
|
||||
logger.exception("Scheduled inventory discovery failed")
|
||||
await _run_gitea_project_sync()
|
||||
await asyncio.sleep(interval_seconds)
|
||||
Reference in new issue
Block a user