diff --git a/cmd/obol/main.go b/cmd/obol/main.go index 1ba26e5..7810aea 100644 --- a/cmd/obol/main.go +++ b/cmd/obol/main.go @@ -46,7 +46,7 @@ COMMANDS: stack down Stop the Obol Stack stack purge Delete stack config (use --force to also delete data) Obol Agent: - agent init Initialize the Obol Agent with an API key + agent init Initialize the Obol Agent Network Management: network list List available networks network install Install and deploy network to cluster @@ -166,18 +166,9 @@ GLOBAL OPTIONS: Subcommands: []*cli.Command{ { Name: "init", - Usage: "Initialize the Obol Agent with an API key", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "agent-api-key", - Aliases: []string{"a"}, - Usage: "API key for the Obol Agent", - EnvVars: []string{"AGENT_API_KEY"}, - }, - }, + Usage: "Initialize the Obol Agent", Action: func(c *cli.Context) error { - agentAPIKey := c.String("agent-api-key") - return agent.Init(cfg, agentAPIKey) + return agent.Init(cfg) }, }, }, diff --git a/internal/agent/agent.go b/internal/agent/agent.go index ada5b2e..a543f56 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -1,91 +1,14 @@ package agent import ( - "fmt" - "io" - "os" - "os/exec" - "path/filepath" - "strings" - "github.com/ObolNetwork/obol-stack/internal/config" - "github.com/ObolNetwork/obol-stack/internal/stack" -) - -const ( - kubeconfigFile = "kubeconfig.yaml" + "github.com/ObolNetwork/obol-stack/internal/openclaw" ) -// Init initializes the Obol Agent with required secrets -func Init(cfg *config.Config, agentAPIKey string) error { - kubeconfigPath := filepath.Join(cfg.ConfigDir, kubeconfigFile) - - // Check if kubeconfig exists (stack must be running) - if _, err := os.Stat(kubeconfigPath); os.IsNotExist(err) { - return fmt.Errorf("stack not running, use 'obol stack up' first") - } - - // Get stack ID for logging - stackID := stack.GetStackID(cfg) - if stackID == "" { - return fmt.Errorf("stack ID not found, run 'obol stack init' first") - } - - // If no API key provided via flag, try to read from stdin - if agentAPIKey == "" { - stat, _ := os.Stdin.Stat() - if (stat.Mode() & os.ModeCharDevice) == 0 { - // Data is being piped to stdin - data, err := io.ReadAll(os.Stdin) - if err == nil { - agentAPIKey = strings.TrimSpace(string(data)) - } - } - } - - // Validate Agent API key was provided - if agentAPIKey == "" { - return fmt.Errorf("agent API key required via --agent-api-key flag or AGENT_API_KEY environment variable. Navigate to https://aistudio.google.com/api-keys to create an API key for your Obol Agent") - } - - fmt.Println("Initializing Obol Agent") - fmt.Printf("Stack ID: %s\n", stackID) - fmt.Println("Creating API key secret for Obol Agent") - - kubectlPath := filepath.Join(cfg.BinDir, "kubectl") - - // Create namespace (idempotent) - nsCmd := exec.Command(kubectlPath, "--kubeconfig", kubeconfigPath, "create", "namespace", "agent", "--dry-run=client", "-o", "yaml") - nsYAML, err := nsCmd.Output() - if err != nil { - return fmt.Errorf("failed to generate namespace manifest: %w", err) - } - - applyNs := exec.Command(kubectlPath, "--kubeconfig", kubeconfigPath, "apply", "-f", "-") - applyNs.Stdin = strings.NewReader(string(nsYAML)) - applyNs.Stdout = os.Stdout - applyNs.Stderr = os.Stderr - if err := applyNs.Run(); err != nil { - return fmt.Errorf("failed to create agent namespace: %w", err) - } - - // Create secret (idempotent) - secretCmd := exec.Command(kubectlPath, "--kubeconfig", kubeconfigPath, "create", "secret", "generic", "obol-agent-api-key", "--from-literal=AGENT_API_KEY="+agentAPIKey, "--namespace=agent", "--dry-run=client", "-o", "yaml") - secretYAML, err := secretCmd.Output() - if err != nil { - return fmt.Errorf("failed to generate secret manifest: %w", err) - } - - applySecret := exec.Command(kubectlPath, "--kubeconfig", kubeconfigPath, "apply", "-f", "-") - applySecret.Stdin = strings.NewReader(string(secretYAML)) - applySecret.Stdout = os.Stdout - applySecret.Stderr = os.Stderr - if err := applySecret.Run(); err != nil { - return fmt.Errorf("failed to create Agent API key secret: %w", err) - } - - fmt.Println("Agent API key secret created") - fmt.Println("Obol Agent initialized successfully") - - return nil +// Init sets up an Obol Agent by running the OpenClaw onboard flow. +func Init(cfg *config.Config) error { + return openclaw.Onboard(cfg, openclaw.OnboardOptions{ + Sync: true, + Interactive: true, + }) } diff --git a/internal/embed/infrastructure/base/templates/obol-agent.yaml b/internal/embed/infrastructure/base/templates/obol-agent.yaml index 7451db7..e708b64 100644 --- a/internal/embed/infrastructure/base/templates/obol-agent.yaml +++ b/internal/embed/infrastructure/base/templates/obol-agent.yaml @@ -98,8 +98,9 @@ subjects: --- #------------------------------------------------------------------------------ -# Deployment - Obol Agent Application -# The agent provides AI-powered Kubernetes and Obol cluster management via MCP +# Deployment - Obol Agent +# Lightweight sidecar that keeps the agent namespace healthy. +# The main agent logic is managed via `obol openclaw`. #------------------------------------------------------------------------------ apiVersion: apps/v1 kind: Deployment @@ -109,7 +110,7 @@ metadata: labels: app: obol-agent spec: - replicas: 1 # Single instance deployment + replicas: 1 selector: matchLabels: app: obol-agent @@ -118,68 +119,19 @@ spec: labels: app: obol-agent spec: - serviceAccountName: obol-agent # Uses the ServiceAccount created above for RBAC + serviceAccountName: obol-agent containers: - name: obol-agent - image: us-east4-docker.pkg.dev/prj-d-playgrounds-f0cb/obol-agent/obol-agent-ag-ui:latest - imagePullPolicy: Always # Always pull latest image - ports: - - name: http - containerPort: 8000 - protocol: TCP - env: - # REQUIRED: Agent API key from Kubernetes secret - # Secret created via: obol agent init --agent-api-key= - - name: AGENT_API_KEY - valueFrom: - secretKeyRef: - name: obol-agent-api-key - key: AGENT_API_KEY - optional: true # Allow deployment even if secret doesn't exist - - # PUBLIC_MODE controls Kubernetes MCP access - # false = Enable Kubernetes API access (uses RBAC permissions above) - # true = Disable Kubernetes API access (for public deployments) - - name: PUBLIC_MODE - value: "false" - - # OKR-1: Default LLM backend via llms.py + Ollama Cloud - # - # The Obol Stack agent is provider-agnostic: - # - `llms.py` (LLMSpy) exposes an OpenAI-compatible API at /v1 - # - LLMSpy forwards to Ollama (in-cluster), which can run `*:cloud` models - # - # Important: Ollama Cloud requires a one-time "connect" of the pod identity - # (public key derived from /root/.ollama/id_ed25519). We persist that key - # in the `llm/ollama-home` PVC so upgrades/restarts don't require re-connect. - - name: LLM_BACKEND - value: "llmspy" - - name: LLM_MODEL - value: "glm-4.7:cloud" - - name: OPENAI_API_BASE - value: "http://llmspy.llm.svc.cluster.local:8000/v1" - - name: OPENAI_API_KEY - value: "ollama" - - # Health checks ensure the pod is ready to receive traffic - livenessProbe: - httpGet: - path: /health - port: http - initialDelaySeconds: 30 - periodSeconds: 10 - readinessProbe: - httpGet: - path: /health - port: http - initialDelaySeconds: 10 - periodSeconds: 5 - - # Resource limits prevent the agent from consuming too many cluster resources + image: busybox:1.37 + command: ["/bin/sh", "-c"] + args: + - | + echo "Use 'obol openclaw' to control your Obol Agent" + while true; do sleep 3600; done resources: - limits: # Maximum allowed resources - cpu: 2000m # 2 CPU cores - memory: 4Gi # 4 GiB RAM + limits: + cpu: 10m + memory: 16Mi --- #------------------------------------------------------------------------------ diff --git a/internal/embed/infrastructure/helmfile.yaml b/internal/embed/infrastructure/helmfile.yaml index 5501463..28acad7 100644 --- a/internal/embed/infrastructure/helmfile.yaml +++ b/internal/embed/infrastructure/helmfile.yaml @@ -29,8 +29,7 @@ releases: values: - dataDir: /data - network: "{{ .Values.network }}" - # obol-agent is disabled by default (image not publicly available). - # Set obolAgent.enabled=true to deploy it. + # obol-agent namespace and RBAC. Set obolAgent.enabled=true to deploy. - obolAgent: enabled: false diff --git a/internal/embed/infrastructure/values/obol-frontend.yaml.gotmpl b/internal/embed/infrastructure/values/obol-frontend.yaml.gotmpl index 0f45cde..5ddebaa 100644 --- a/internal/embed/infrastructure/values/obol-frontend.yaml.gotmpl +++ b/internal/embed/infrastructure/values/obol-frontend.yaml.gotmpl @@ -35,7 +35,7 @@ image: repository: obolnetwork/obol-stack-front-end pullPolicy: Always - tag: "v0.1.4" + tag: "sha-14f85b5" service: type: ClusterIP