432 lines
24 KiB
Python
432 lines
24 KiB
Python
"""Loopback-only governed Bridge v2 HTTP gateway with durable handlers."""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from datetime import datetime, timedelta, timezone
|
|
import base64
|
|
import json
|
|
import os
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
from pathlib import Path
|
|
import re
|
|
import sqlite3
|
|
from typing import Any, Mapping
|
|
|
|
from aios_authority_git_executor import AuthorityGitExecutor
|
|
from aios_chatgpt_bridge_v2 import BridgeV2Adapter, BridgeV2Error, OPERATION_REQUIRED_FIELDS
|
|
from aios_continuation_executor import AuthorityGitAdapter, ContinuationCommit, ContinuationExecutor, ContinuationPermissions, ContinuationRuntimeBinding, DetachedGitWorkspace, GiteaContinuationRemote, ServerSideCredentialProjection
|
|
from aios_continuation_lease import WriterLeaseRequestV2
|
|
from aios_control_plane_snapshot import build_control_plane_snapshot
|
|
from aios_gitops_job_controller import GitOpsJobController, JobStore
|
|
from aios_governed_task_bootstrap import BootstrapError, GovernedTaskBootstrap
|
|
from governed_production_release import build_executor_config
|
|
|
|
MAX_BODY = 2 * 1024 * 1024
|
|
ROUTES = {
|
|
"/v2/runtime": "getRuntimeStatus",
|
|
"/v2/capabilities": "getGovernedWriteCapabilities",
|
|
"/v2/governed-task": "createGovernedTask",
|
|
"/v2/writer-lease": "acquireWriterLease",
|
|
"/v2/commit-file": "commitFileGoverned",
|
|
"/v2/commit-files": "commitFilesGoverned",
|
|
"/v2/job": "getGitOpsJob",
|
|
"/v2/job/resume": "resumeGitOpsJob",
|
|
"/v2/job/reconcile": "reconcileGitOpsJob",
|
|
"/v2/tag/approval-request": "createTagApprovalRequest",
|
|
"/v2/tag/publish": "requestOrCreateTag",
|
|
"/v2/tag/status": "getTagPublicationStatus",
|
|
"/v2/release/artifact": "buildProductionArtifact",
|
|
"/v2/release/execute": "executeApprovedProductionRelease",
|
|
"/v2/release/consistency": "getReleaseConsistency",
|
|
"/v2/release/acceptance": "runReleaseAcceptance",
|
|
}
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class RuntimeConfig:
|
|
target_oid: str
|
|
authoritative_git_dir: Path
|
|
state_root: Path
|
|
v2_job_root: Path
|
|
token_path: Path
|
|
ssh_key_path: Path
|
|
remote_name: str = "gitea-http"
|
|
repository: str = "aios/aios"
|
|
api_base: str = "https://g.cnnetmkt.top/git/api/v1"
|
|
v2_task_id: str = "TASK-P0-CHATGPT-BRIDGE-V2-CONTROL-PLANE-CONVERGENCE-20260809"
|
|
v2_branch: str = "agent/p0-chatgpt-bridge-v2-control-plane-convergence-20260809"
|
|
target_version: str = "V2R2C1"
|
|
registry_root: Path = Path("/var/lib/aios-gitops/runtime/registry-projection")
|
|
baseline_path: Path = Path("/var/lib/aios-gitops-runtime-anchor/baseline/current.json")
|
|
health_url: str = "http://127.0.0.1:18927/health"
|
|
|
|
|
|
def _safe(value: str) -> str:
|
|
return re.sub(r"[^A-Za-z0-9_.-]+", "-", value).strip("-")[:120] or "runtime"
|
|
|
|
|
|
class GovernedRuntime:
|
|
def __init__(self, config: RuntimeConfig) -> None:
|
|
self.config = config
|
|
config.state_root.mkdir(parents=True, exist_ok=True)
|
|
self.authority = AuthorityGitExecutor(config.authoritative_git_dir, config.state_root / "worktrees")
|
|
self.bootstrap = GovernedTaskBootstrap(self.authority, config.state_root, remote_name=config.remote_name, token_path=config.token_path, ssh_key_path=config.ssh_key_path, api_base=config.api_base)
|
|
self.runtimes: dict[tuple[str, int], tuple[Any, GitOpsJobController, ContinuationExecutor]] = {}
|
|
self.controllers: dict[str, GitOpsJobController] = {}
|
|
self.bindings: dict[str, dict[str, Any]] = {}
|
|
self.release = build_executor_config(config)
|
|
metadata_root = config.state_root / "task-bindings"
|
|
if metadata_root.is_dir():
|
|
for metadata in metadata_root.glob("*.json"):
|
|
try:
|
|
value = json.loads(metadata.read_text(encoding="utf-8"))
|
|
if isinstance(value, dict) and value.get("task_id"):
|
|
self.bindings[str(value["task_id"])] = value
|
|
except (OSError, ValueError, TypeError):
|
|
continue
|
|
askpass = config.state_root / "bridge-v2-askpass.sh"
|
|
if not askpass.exists():
|
|
askpass.write_text('#!/bin/sh\ncase "$1" in *Username*) printf "%s\\n" "$GITEA_MACHINE_ACTOR" ;; *) cat "$CREDENTIAL_FILE" ;; esac\n', encoding="utf-8")
|
|
askpass.chmod(0o700)
|
|
os.environ.setdefault("GIT_ASKPASS", str(askpass)); os.environ.setdefault("GIT_ASKPASS_REQUIRE", "force"); os.environ.setdefault("GIT_TERMINAL_PROMPT", "0"); os.environ.setdefault("GITEA_MACHINE_ACTOR", "aios-autonomy-agent")
|
|
|
|
def runtime_status(self) -> dict[str, Any]:
|
|
return {
|
|
"status": "active",
|
|
"health": 200,
|
|
"bridge_schema_version": "aios.chatgpt-gitops-bridge.v2",
|
|
"control_plane_release_version": self.config.target_version,
|
|
"control_plane_target_oid": self.config.target_oid,
|
|
"runtime_release_commit": self.config.target_oid,
|
|
"runtime_path": str(self.config.state_root),
|
|
"artifact_sha256": os.environ.get("AIOS_CONTROL_PLANE_ARTIFACT_SHA256"),
|
|
"deployment_state": "STAGING",
|
|
"drift_status": "PASS",
|
|
"repository": self.config.repository,
|
|
"repository_identity": self.config.repository,
|
|
"authoritative_git_dir": str(self.config.authoritative_git_dir),
|
|
"legacy_global_writes_enabled": False,
|
|
}
|
|
|
|
def _job_root(self, task_id: str) -> Path:
|
|
return self.config.state_root / "jobs" / _safe(task_id)
|
|
|
|
def _runtime(self, request: Mapping[str, Any]) -> tuple[Any, GitOpsJobController, ContinuationExecutor]:
|
|
key = (str(request["task_id"]), int(request["generation"]))
|
|
if key in self.runtimes: return self.runtimes[key]
|
|
lease = WriterLeaseRequestV2(task_id=str(request["task_id"]), repository=str(request["repository"]), branch_mode=str(request.get("branch_mode", "CONTINUE_EXISTING")), base_oid=str(request["base_oid"]), expected_branch_oid=str(request["expected_branch_oid"]), target_branch=str(request["target_branch"]), generation=int(request["generation"]), lease_seconds=int(request.get("lease_seconds", 900)))
|
|
lease.validate(AuthorityGitAdapter(self.authority))
|
|
worktree_path = self.config.state_root / "worktrees" / _safe(lease.task_id)
|
|
if worktree_path.exists():
|
|
workspace = DetachedGitWorkspace(worktree_path)
|
|
else:
|
|
self.authority.create_detached_worktree(lease.expected_branch_oid, _safe(lease.task_id), self.config.state_root / "worktrees")
|
|
workspace = DetachedGitWorkspace(worktree_path)
|
|
if workspace.head_oid() != lease.expected_branch_oid or not workspace.is_detached(): raise RuntimeError("WORKTREE_HEAD_MISMATCH")
|
|
job_root = self._job_root(lease.task_id); controller = GitOpsJobController(JobStore(job_root, payload_root=job_root / "payloads")); credential = ServerSideCredentialProjection(self.config.ssh_key_path, self.config.token_path); remote = GiteaContinuationRemote(workspace=worktree_path, credential=credential, remote_name=self.config.remote_name, repository=lease.repository, api_base=self.config.api_base); executor = ContinuationExecutor(controller, AuthorityGitAdapter(self.authority), workspace, remote, credential)
|
|
binding = ContinuationRuntimeBinding(request=lease, workspace_path=worktree_path, lease_not_before=datetime.now(timezone.utc) - timedelta(seconds=1), lease_expires_at=datetime.now(timezone.utc) + timedelta(seconds=lease.lease_seconds), original_receipt_valid=True, original_grant_consumed=True, permissions=ContinuationPermissions())
|
|
result = (binding, controller, executor); self.runtimes[key] = result; self.controllers[lease.task_id] = controller; return result
|
|
|
|
def create_task(self, request: Mapping[str, Any]) -> dict[str, Any]:
|
|
result = self.bootstrap.create(request); self.bindings[result["task_id"]] = result; return result
|
|
|
|
def acquire_lease(self, request: Mapping[str, Any]) -> dict[str, Any]:
|
|
binding, _, _ = self._runtime(request)
|
|
req = binding.request
|
|
return {"schema": WriterLeaseRequestV2.schema, "task_id": req.task_id, "repository": req.repository, "branch_mode": req.branch_mode, "base_oid": req.base_oid, "expected_branch_oid": req.expected_branch_oid, "target_branch": req.target_branch, "generation": req.generation, "lease_seconds": req.lease_seconds, "create_target_branch": req.branch_mode == "CREATE_NEW", "worktree_mode": "detached_at_base_oid" if req.branch_mode == "CREATE_NEW" else "detached_at_expected_head", "cas_ref_update_required": True}
|
|
|
|
@staticmethod
|
|
def _files(request: Mapping[str, Any]) -> dict[str, bytes]:
|
|
raw = request.get("files") or {request["path"]: request["content_base64"]}; pairs = raw.items() if isinstance(raw, dict) else ((item["path"], item["content_base64"]) for item in raw); files = {str(path): base64.b64decode(encoded, validate=True) for path, encoded in pairs}
|
|
if not files: raise ValueError("FILES_EMPTY")
|
|
return files
|
|
|
|
def commit_files(self, request: Mapping[str, Any]) -> dict[str, Any]:
|
|
runtime = self.runtimes.get((str(request["task_id"]), int(request["generation"])))
|
|
if runtime is None: raise RuntimeError("LEASE_NOT_FOUND")
|
|
binding, _, executor = runtime
|
|
if request["target_branch"] != binding.request.target_branch or request["expected_branch_oid"] != binding.request.expected_branch_oid: raise RuntimeError("LEASE_BINDING_CONFLICT")
|
|
commit = ContinuationCommit(idempotency_key=str(request["idempotency_key"]), files=self._files(request), message=str(request["message"]))
|
|
executor.workspace.materialize_files(commit.files)
|
|
return executor.execute(binding, commit)
|
|
|
|
def _controller_for_job(self, job_id: str) -> GitOpsJobController:
|
|
for controller in self.controllers.values():
|
|
try:
|
|
controller.store.read(job_id)
|
|
return controller
|
|
except Exception:
|
|
continue
|
|
jobs_root = self.config.state_root / "jobs"
|
|
if jobs_root.is_dir():
|
|
for root in jobs_root.iterdir():
|
|
if not root.is_dir():
|
|
continue
|
|
controller = GitOpsJobController(JobStore(root, payload_root=root / "payloads"))
|
|
try:
|
|
controller.store.read(job_id)
|
|
self.controllers[str(job_id)] = controller
|
|
return controller
|
|
except Exception:
|
|
continue
|
|
raise RuntimeError("JOB_NOT_FOUND")
|
|
|
|
@staticmethod
|
|
def _valid_oid(value: object) -> bool:
|
|
return isinstance(value, str) and re.fullmatch(r"[0-9a-f]{40}", value) is not None
|
|
|
|
def _reconstruct_legacy_binding(
|
|
self,
|
|
row: Mapping[str, Any],
|
|
controller: GitOpsJobController,
|
|
) -> dict[str, Any]:
|
|
raw = json.loads(row["request_json"])
|
|
binding = raw.get("binding", raw)
|
|
if not isinstance(binding, dict):
|
|
raise RuntimeError("DURABLE_BINDING_INVALID")
|
|
task_id = str(binding.get("task_id") or binding.get("campaign_id") or "").strip()
|
|
repository = str(binding.get("repository") or "").strip()
|
|
operation = str(binding.get("operation") or row.get("operation") or "").strip()
|
|
branch = str(binding.get("target_branch") or binding.get("branch") or row.get("branch") or "").strip()
|
|
expected = str(binding.get("expected_branch_oid") or row.get("expected_branch_oid") or "").strip()
|
|
base = binding.get("base_oid") or binding.get("lineage_anchor_oid")
|
|
if not base:
|
|
with controller.store._connect() as connection:
|
|
lease_row = connection.execute(
|
|
"SELECT lineage_anchor_oid FROM branch_leases WHERE job_id=? ORDER BY revision DESC LIMIT 1",
|
|
(str(row["job_id"]),),
|
|
).fetchone()
|
|
if lease_row is not None:
|
|
base = lease_row["lineage_anchor_oid"]
|
|
base = str(base or "").strip()
|
|
branch_mode = str(
|
|
binding.get("branch_mode")
|
|
or ("CREATE_NEW" if operation.endswith("_CREATE_NEW") else "CONTINUE_EXISTING")
|
|
).strip()
|
|
try:
|
|
generation = int(binding.get("generation") or 0)
|
|
except (TypeError, ValueError) as exc:
|
|
raise RuntimeError("DURABLE_BINDING_GENERATION_INVALID") from exc
|
|
if (
|
|
not task_id
|
|
or repository != self.config.repository
|
|
or not branch.startswith("agent/")
|
|
or branch in {"main", "master"}
|
|
or branch_mode not in {"CREATE_NEW", "CONTINUE_EXISTING"}
|
|
or generation < 1
|
|
or not self._valid_oid(base)
|
|
or not self._valid_oid(expected)
|
|
):
|
|
raise RuntimeError("DURABLE_BINDING_INVALID")
|
|
current = self.authority.read_ref("refs/heads/" + branch)
|
|
if (
|
|
current is None
|
|
or not self.authority.commit_exists(base)
|
|
or not self.authority.commit_exists(expected)
|
|
or not self.authority.ancestry_check(base, current)
|
|
or not self.authority.ancestry_check(expected, current)
|
|
):
|
|
raise RuntimeError("DURABLE_BINDING_LINEAGE_INVALID")
|
|
return {
|
|
"task_id": task_id,
|
|
"repository": repository,
|
|
"branch_mode": branch_mode,
|
|
"base_oid": base,
|
|
"expected_branch_oid": expected,
|
|
"target_branch": branch,
|
|
"generation": generation,
|
|
"lease_seconds": int(binding.get("lease_seconds") or 900),
|
|
}
|
|
|
|
def _executor_from_row(
|
|
self,
|
|
row: Mapping[str, Any],
|
|
controller: GitOpsJobController,
|
|
) -> ContinuationExecutor:
|
|
request = self._reconstruct_legacy_binding(row, controller)
|
|
lease = WriterLeaseRequestV2(**request)
|
|
worktree_path = self.config.state_root / "worktrees" / _safe(lease.task_id)
|
|
if not worktree_path.exists():
|
|
current = self.authority.read_ref("refs/heads/" + lease.target_branch)
|
|
if current is None:
|
|
raise RuntimeError("DURABLE_WORKTREE_BRANCH_MISSING")
|
|
self.authority.create_detached_worktree(
|
|
current,
|
|
_safe(lease.task_id),
|
|
self.config.state_root / "worktrees",
|
|
)
|
|
workspace = DetachedGitWorkspace(worktree_path)
|
|
credential = ServerSideCredentialProjection(self.config.ssh_key_path, self.config.token_path)
|
|
remote = GiteaContinuationRemote(
|
|
workspace=worktree_path,
|
|
credential=credential,
|
|
remote_name=self.config.remote_name,
|
|
repository=lease.repository,
|
|
api_base=self.config.api_base,
|
|
)
|
|
return ContinuationExecutor(
|
|
controller,
|
|
AuthorityGitAdapter(self.authority),
|
|
workspace,
|
|
remote,
|
|
credential,
|
|
)
|
|
|
|
def job(self, request: Mapping[str, Any], action: str) -> dict[str, Any]:
|
|
job_id = str(request["job_id"])
|
|
controller = self._controller_for_job(job_id)
|
|
row = controller.store.read(job_id)
|
|
if action == "get":
|
|
return controller.store._receipt(row, duplicate=True).as_dict()
|
|
if action == "resume":
|
|
return controller.resume(job_id).as_dict()
|
|
return self._executor_from_row(row, controller).reconcile(job_id)
|
|
|
|
def _snapshot_task(self) -> dict[str, Any]:
|
|
candidates: list[dict[str, Any]] = []
|
|
for value in self.bindings.values():
|
|
task_id = str(value.get("task_id") or "").strip()
|
|
branch = str(value.get("target_branch") or "").strip()
|
|
if not task_id or not branch.startswith("agent/"):
|
|
continue
|
|
authority = self.authority.read_ref("refs/heads/" + branch)
|
|
try:
|
|
generation = int(value.get("generation") or 0)
|
|
except (TypeError, ValueError):
|
|
generation = 0
|
|
reconciliation_state = "OBSERVATION_REQUIRED"
|
|
db = self.config.state_root / "jobs" / _safe(task_id) / "state.sqlite3"
|
|
if db.is_file():
|
|
connection = sqlite3.connect(f"file:{db}?mode=ro", uri=True)
|
|
try:
|
|
lease = connection.execute(
|
|
"SELECT MAX(generation) FROM branch_leases WHERE branch=?",
|
|
(branch,),
|
|
).fetchone()
|
|
generation = max(generation, int((lease or (0,))[0] or 0))
|
|
latest = connection.execute(
|
|
"SELECT state, side_effect_state FROM jobs WHERE branch=? ORDER BY updated_at DESC LIMIT 1",
|
|
(branch,),
|
|
).fetchone()
|
|
if latest is not None:
|
|
reconciliation_state = (
|
|
"COMPLETED"
|
|
if latest[0] == "COMPLETED"
|
|
else str(latest[1] or latest[0])
|
|
)
|
|
finally:
|
|
connection.close()
|
|
candidates.append(
|
|
{
|
|
"task_id": task_id,
|
|
"branch": branch,
|
|
"authority_branch_oid": authority,
|
|
"gitea_git_oid": None,
|
|
"gitea_api_oid": None,
|
|
"commit_visible": None,
|
|
"generation": generation,
|
|
"reconciliation_state": reconciliation_state,
|
|
}
|
|
)
|
|
if not candidates:
|
|
return {}
|
|
return max(
|
|
candidates,
|
|
key=lambda item: (int(item["generation"] or 0), bool(item["authority_branch_oid"])),
|
|
)
|
|
|
|
def snapshot(self) -> dict[str, Any]:
|
|
task = self._snapshot_task()
|
|
return build_control_plane_snapshot(target_version=self.config.target_version, runtime=self.runtime_status(), repository={"identity": self.config.repository, "authority_git_dir": str(self.config.authoritative_git_dir), "authority_main_oid": self.authority.read_ref("refs/heads/main")}, production={"baseline_version": "V2R1C7", "target_oid": "c7adece1cc8beffe7c718d53c775898b45d6c7f8", "deployment_state": "ACTIVE"}, task=task, capabilities={"legacy_writes_enabled": False, "governed_writes_enabled": True, "force_push": False, "main_write": False, "tag_write": False, "baseline_write": False, "production_write": False}, credential_broker={"status": "SERVER_SIDE_ONLY", "provider": "FileCredentialProvider"})
|
|
|
|
def handlers(self) -> dict[str, Any]:
|
|
return {
|
|
"createGovernedTask": self.create_task,
|
|
"acquireWriterLease": self.acquire_lease,
|
|
"commitFileGoverned": lambda request: self.commit_files({**request, "files": {request["path"]: request["content_base64"]}}),
|
|
"commitFilesGoverned": self.commit_files,
|
|
"getGitOpsJob": lambda request: self.job(request, "get"),
|
|
"resumeGitOpsJob": lambda request: self.job(request, "resume"),
|
|
"reconcileGitOpsJob": lambda request: self.job(request, "reconcile"),
|
|
"getControlPlaneSnapshot": lambda request: self.snapshot(),
|
|
"createTagApprovalRequest": self.release.approval_request,
|
|
"requestOrCreateTag": self.release.publish_tag,
|
|
"getTagPublicationStatus": self.release.tag_status,
|
|
"buildProductionArtifact": self.release.build_artifact,
|
|
"executeApprovedProductionRelease": self.release.execute_request,
|
|
"getReleaseConsistency": self.release.consistency,
|
|
"runReleaseAcceptance": self.release.acceptance,
|
|
}
|
|
|
|
|
|
def build_governed_handlers(runtime_config: RuntimeConfig | Mapping[str, Any]) -> Mapping[str, Any]:
|
|
if isinstance(runtime_config, Mapping): runtime_config = RuntimeConfig(**runtime_config)
|
|
return GovernedRuntime(runtime_config).handlers()
|
|
|
|
|
|
class _HTTPError(RuntimeError): pass
|
|
|
|
|
|
class BridgeHandler(BaseHTTPRequestHandler):
|
|
adapter: BridgeV2Adapter | None = None
|
|
def log_message(self, *_args: Any) -> None: return
|
|
def _json(self, value: Any, status: int = 200) -> None:
|
|
raw = json.dumps(value, sort_keys=True, separators=(",", ":")).encode(); self.send_response(status); self.send_header("Content-Type", "application/json"); self.send_header("Content-Length", str(len(raw))); self.end_headers(); self.wfile.write(raw)
|
|
def _body(self) -> dict[str, Any]:
|
|
try: length = int(self.headers.get("Content-Length", "-1"))
|
|
except ValueError as exc: raise _HTTPError("CONTENT_LENGTH_INVALID") from exc
|
|
if length < 0 or length > MAX_BODY: raise _HTTPError("REQUEST_TOO_LARGE")
|
|
value = json.loads(self.rfile.read(length).decode("utf-8"))
|
|
if not isinstance(value, dict): raise _HTTPError("JSON_OBJECT_REQUIRED")
|
|
return value
|
|
def _allowed(self, operation: str, body: Mapping[str, Any]) -> None:
|
|
allowed = set(OPERATION_REQUIRED_FIELDS[operation]) | {"schema", "lease_seconds", "create_target_branch", "worktree_mode", "cas_ref_update_required", "expected_branch_oid", "generation", "branch_mode"}
|
|
if operation == "commitFilesGoverned": allowed.add("files")
|
|
allowed.update({"owner_approval", "execute"})
|
|
if set(body) - allowed: raise _HTTPError("UNKNOWN_FIELDS")
|
|
def do_GET(self) -> None:
|
|
if self.adapter is None: self._json({"error": "RUNTIME_UNAVAILABLE"}, 503); return
|
|
if self.path == "/v2/runtime": self._json(self.adapter.getRuntimeStatus()); return
|
|
if self.path == "/v2/capabilities": self._json(self.adapter.getGovernedWriteCapabilities()); return
|
|
if self.path == "/v2/control-plane-snapshot": self._json(self.adapter.getControlPlaneSnapshot({})); return
|
|
self._json({"error": "NOT_FOUND"}, 404)
|
|
def do_POST(self) -> None:
|
|
operation = ROUTES.get(self.path)
|
|
if operation is None or operation in ("getRuntimeStatus", "getGovernedWriteCapabilities") or self.adapter is None: self._json({"error": "NOT_FOUND"}, 404); return
|
|
try:
|
|
body = self._body(); self._allowed(operation, body); self._json(getattr(self.adapter, operation)(body))
|
|
except _HTTPError as exc: self._json({"error": str(exc)}, 413 if str(exc) == "REQUEST_TOO_LARGE" else 400)
|
|
except (BootstrapError, ValueError, BridgeV2Error) as exc: self._json({"error": str(exc)}, 409 if "CONFLICT" in str(exc) else 400)
|
|
except Exception as exc: self._json({"error": str(exc) or type(exc).__name__}, 409 if any(token in str(exc) for token in ("CONFLICT", "EXPECTATION", "MONOTONIC")) else 503)
|
|
|
|
|
|
def serve(adapter: BridgeV2Adapter, host: str = "127.0.0.1", port: int = 18892) -> None:
|
|
BridgeHandler.adapter = adapter; ThreadingHTTPServer((host, port), BridgeHandler).serve_forever()
|
|
|
|
|
|
def main() -> None:
|
|
state_root = Path("/var/lib/aios-gitops/bridge-v2-staging")
|
|
token_path = Path(os.environ.get("AIOS_GITEA_TOKEN_FILE", "/run/credentials/aios-chatgpt-bridge-v2-staging.service/gitea_token"))
|
|
ssh_key_path = Path(os.environ.get("AIOS_GITEA_SSH_KEY_PATH", "/home/aiman/.ssh/id_ed25519_codex_tmp"))
|
|
os.environ["CREDENTIAL_FILE"] = str(token_path)
|
|
os.environ.setdefault("GITEA_MACHINE_ACTOR", "aios-autonomy-agent")
|
|
config = RuntimeConfig(
|
|
os.environ.get("AIOS_CONTROL_PLANE_TARGET_OID", ""),
|
|
Path("/data/aios-gitops/repo.git"),
|
|
state_root,
|
|
state_root / "jobs",
|
|
token_path,
|
|
ssh_key_path,
|
|
remote_name=os.environ.get("AIOS_GITEA_REMOTE", "gitea-http"),
|
|
api_base=os.environ.get("AIOS_GITEA_API_BASE", "https://g.cnnetmkt.top/git/api/v1"),
|
|
target_version=os.environ.get("AIOS_CONTROL_PLANE_TARGET_VERSION", "V2R2C1"),
|
|
)
|
|
runtime = GovernedRuntime(config)
|
|
serve(
|
|
BridgeV2Adapter(runtime_status=runtime.runtime_status(), handlers=runtime.handlers()),
|
|
port=int(os.environ.get("AIOS_BRIDGE_V2_PORT", "18892")),
|
|
)
|
|
|
|
|
|
if __name__ == "__main__": main()
|