"""Lay down the backend's data dir before it boots, so the run is ready on the first tick. Everything here is written pre-boot on purpose: the workflow store and the settings store both load from disk once at startup, so seeding files is cheaper and more deterministic than replaying create/PATCH calls over HTTP (no aux LLM naming call, no schedule normalization, no chance of the container inventing a second workflow). """ import json import os import tempfile from typing import Any, Dict from typeguard import typechecked from backend.apps.dashboards.models import Dashboard from backend.apps.settings.models import AppSettings from runner.run_spec import CLOUD_RUN_DASHBOARD_ID, RunSpec # 9Router provider id -> the AppSettings field the backend reads a raw key from. API_KEY_SETTINGS_FIELD: Dict[str, str] = { "anthropic": "anthropic_api_key", "openai": "openai_api_key", "gemini": "google_api_key", "google": "google_api_key", "openrouter": "openrouter_api_key", } # One synthetic id for every cloud run. Without it each ephemeral container mints a fresh uuid and analytics sees a brand-new "install" per run. CLOUD_RUNNER_INSTALLATION_ID = "openswarm-cloud-runner" @typechecked def p_write_json(path: str, payload: Any) -> None: """Atomic, owner-only write; these files hold API keys.""" directory = os.path.dirname(path) os.makedirs(directory, mode=0o700, exist_ok=True) handle, temp_path = tempfile.mkstemp(dir=directory, prefix=".seed-", suffix=".json") try: with os.fdopen(handle, "w", encoding="utf-8") as stream: json.dump(payload, stream, indent=2) os.chmod(temp_path, 0o600) os.replace(temp_path, path) except BaseException: if os.path.exists(temp_path): os.unlink(temp_path) raise @typechecked def settings_for_run(spec: RunSpec) -> AppSettings: """The AppSettings a cloud run needs: this workflow's model, this run's keys, no telemetry.""" settings = AppSettings() settings.default_model = spec.workflow.model settings.connection_mode = "own_key" settings.analytics_opt_in = False settings.installation_id = CLOUD_RUNNER_INSTALLATION_ID for credential in spec.credentials: if credential.auth_type != "api_key": continue field = API_KEY_SETTINGS_FIELD.get(credential.provider) if field is None: raise ValueError( f"no settings field for api_key provider {credential.provider!r}; " f"supported: {', '.join(sorted(set(API_KEY_SETTINGS_FIELD)))}" ) setattr(settings, field, credential.api_key) return settings @typechecked def seed_data_root(data_root: str, spec: RunSpec) -> None: """Write the workflow, settings and dashboard records the backend will read at boot. The dashboard exists so the Electron window has somewhere to land and browser cards have somewhere to render. Writing it here rather than letting the backend's first-boot migration invent one keeps its id knowable before anything has started. """ workflow = spec.workflow_for_disk() p_write_json( os.path.join(data_root, "workflows", f"{workflow.id}.json"), workflow.model_dump(mode="json"), ) p_write_json( os.path.join(data_root, "settings", "settings.json"), settings_for_run(spec).model_dump(mode="json"), ) dashboard = Dashboard(id=CLOUD_RUN_DASHBOARD_ID, name=spec.workflow.title or "Cloud run") p_write_json( os.path.join(data_root, "dashboards", f"{dashboard.id}.json"), dashboard.model_dump(mode="json"), )