From ace220a0ca26ae2260ce28da68e4c738dc63ff02 Mon Sep 17 00:00:00 2001 From: Zexi Li Date: Fri, 13 Mar 2026 20:38:28 +0800 Subject: [PATCH] fix(llm): mount openclaw home dir (#24449) --- pkg/apis/llm/llm_const.go | 8 --- pkg/apis/llm/openclaw.go | 42 +++++++++++++ pkg/hostman/container/volume_mount/text.go | 3 + pkg/llm/drivers/llm_container/openclaw.go | 73 ++++++++++++++++++++-- 4 files changed, 114 insertions(+), 12 deletions(-) create mode 100644 pkg/apis/llm/openclaw.go diff --git a/pkg/apis/llm/llm_const.go b/pkg/apis/llm/llm_const.go index 4a3f92be62..2e18e751e9 100644 --- a/pkg/apis/llm/llm_const.go +++ b/pkg/apis/llm/llm_const.go @@ -81,11 +81,3 @@ const ( ) type LLMEnvKey string - -const ( - LLM_OPENCLAW_GATEWAY_TOKEN = LLMEnvKey("OPENCLAW_GATEWAY_TOKEN") - LLM_OPENCLAW_AUTH_USERNAME = LLMEnvKey("AUTH_USERNAME") - LLM_OPENCLAW_CUSTOM_USER = LLMEnvKey("CUSTOM_USER") - LLM_OPENCLAW_AUTH_PASSWORD = LLMEnvKey("AUTH_PASSWORD") - LLM_OPENCLAW_PASSWORD = LLMEnvKey("PASSWORD") -) diff --git a/pkg/apis/llm/openclaw.go b/pkg/apis/llm/openclaw.go new file mode 100644 index 0000000000..854bec34e1 --- /dev/null +++ b/pkg/apis/llm/openclaw.go @@ -0,0 +1,42 @@ +package llm + +const ( + LLM_OPENCLAW_GATEWAY_TOKEN = LLMEnvKey("OPENCLAW_GATEWAY_TOKEN") + LLM_OPENCLAW_AUTH_USERNAME = LLMEnvKey("AUTH_USERNAME") + LLM_OPENCLAW_CUSTOM_USER = LLMEnvKey("CUSTOM_USER") + LLM_OPENCLAW_AUTH_PASSWORD = LLMEnvKey("AUTH_PASSWORD") + LLM_OPENCLAW_PASSWORD = LLMEnvKey("PASSWORD") + LLM_OPENCLAW_CUSTOM_CONFIG = LLMEnvKey("OPENCLAW_CUSTOM_CONFIG") + LLM_OPENCLAW_CUSTOM_CONFIG_FILE = "/opt/openclaw_base_config.json" + + // templates + LLM_OPENCLAW_TEMPLATE_AGENTS_MD_B64 = LLMEnvKey("OPENCLAW_TEMPLATE_AGENTS_MD_B64") + LLM_OPENCLAW_TEMPLATE_SOUL_MD_B64 = LLMEnvKey("OPENCLAW_TEMPLATE_SOUL_MD_B64") + LLM_OPENCLAW_TEMPLATE_USER_MD_B64 = LLMEnvKey("OPENCLAW_TEMPLATE_USER_MD_B64") +) + +type OpenClawConfig struct { + Browser *OpenClawConfigBrowser `json:"browser"` + Agents *OpenClawConfigAgents `json:"agents"` +} + +type OpenClawConfigAgents map[string]*OpenClawConfigAgent + +type OpenClawConfigAgent struct { + Model *OpenClawConfigAgentModel `json:"model"` + ImageModel *OpenClawConfigAgentModel `json:"imageModel"` + Workspace string `json:"workspace"` +} + +type OpenClawConfigAgentModel struct { + Primary string `json:"primary"` +} + +// ref: https://docs.openclaw.ai/tools/browser#configuration +type OpenClawConfigBrowser struct { + Enabled bool `json:"enabled"` + SSRFPolicy map[string]interface{} `json:"ssrfPolicy"` + DefaultProfile string `json:"defaultProfile"` + Headless bool `json:"headless"` + NoSandbox bool `json:"noSandbox"` +} diff --git a/pkg/hostman/container/volume_mount/text.go b/pkg/hostman/container/volume_mount/text.go index 4004537aab..fc749ff8d5 100644 --- a/pkg/hostman/container/volume_mount/text.go +++ b/pkg/hostman/container/volume_mount/text.go @@ -57,6 +57,9 @@ func (t text) GetRuntimeMountHostPath(pod IPodInfo, ctrId string, vm *hostapi.Co dirPath = rootFsPath } mntPath := filepath.Join(dirPath, fmt.Sprintf("%s-%s", ctrId, strings.ReplaceAll(vm.MountPath, "/", "_"))) + if err := EnsureDir(filepath.Dir(mntPath)); err != nil { + return "", errors.Wrapf(err, "mkdir %s", filepath.Dir(mntPath)) + } if err := t.writeContent(ti, mntPath); err != nil { return "", errors.Wrapf(err, "write content %s to %s", ti, mntPath) } diff --git a/pkg/llm/drivers/llm_container/openclaw.go b/pkg/llm/drivers/llm_container/openclaw.go index c1c5c16f6a..548a912648 100644 --- a/pkg/llm/drivers/llm_container/openclaw.go +++ b/pkg/llm/drivers/llm_container/openclaw.go @@ -2,6 +2,7 @@ package llm_container import ( "context" + "encoding/base64" "fmt" "strings" @@ -23,7 +24,8 @@ const ( // browserContainerName = "browser" // openclawBrowserImage = "registry.cn-beijing.aliyuncs.com/cloudpods/openclaw-browser:latest" openclawDataDir = "/data" - browserConfigDir = "/config" + desktopConfigDir = "/config" + homeDir = "/home/" // openclawBrowserCDPPort = "9222" ) @@ -230,6 +232,28 @@ func (c *openclaw) GetContainerSpec(ctx context.Context, llm *models.SLLM, image // } // } +func (c *openclaw) getOpenClawBaseConfig(llm *models.SLLM) *api.OpenClawConfig { + return &api.OpenClawConfig{ + Browser: &api.OpenClawConfigBrowser{ + Enabled: true, + DefaultProfile: "openclaw", + SSRFPolicy: map[string]interface{}{ + "dangerouslyAllowPrivateNetwork": true, + }, + Headless: false, + NoSandbox: false, + }, + Agents: &api.OpenClawConfigAgents{ + "defaults": &api.OpenClawConfigAgent{ + // TODO: 支持从 llm spec 里面自动选择 + ImageModel: &api.OpenClawConfigAgentModel{ + Primary: "moonshot/kimi-k2.5", + }, + }, + }, + } +} + func (c *openclaw) GetContainerSpecs(ctx context.Context, llm *models.SLLM, image *models.SLLMImage, sku *models.SLLMSku, props []string, devices []computeapi.SIsolatedDevice, diskId string) []*computeapi.PodContainerCreateInput { diskIndex := 0 @@ -240,7 +264,15 @@ func (c *openclaw) GetContainerSpecs(ctx context.Context, llm *models.SLLM, imag SubDirectory: "config", }, Type: commonapi.CONTAINER_VOLUME_MOUNT_TYPE_DISK, - MountPath: browserConfigDir, + MountPath: desktopConfigDir, + }, + { + Disk: &commonapi.ContainerVolumeMountDisk{ + Index: &diskIndex, + SubDirectory: "home", + }, + Type: commonapi.CONTAINER_VOLUME_MOUNT_TYPE_DISK, + MountPath: homeDir, }, { Disk: &commonapi.ContainerVolumeMountDisk{ @@ -250,6 +282,13 @@ func (c *openclaw) GetContainerSpecs(ctx context.Context, llm *models.SLLM, imag Type: commonapi.CONTAINER_VOLUME_MOUNT_TYPE_DISK, MountPath: openclawDataDir, }, + { + Type: commonapi.CONTAINER_VOLUME_MOUNT_TYPE_TEXT, + Text: &commonapi.ContainerVolumeMountText{ + Content: jsonutils.Marshal(c.getOpenClawBaseConfig(llm)).PrettyString(), + }, + MountPath: api.LLM_OPENCLAW_CUSTOM_CONFIG_FILE, + }, } httpAuthUsername := "admin" httpAuthPassword := "clawadmin@123" @@ -268,6 +307,10 @@ func (c *openclaw) GetContainerSpecs(ctx context.Context, llm *models.SLLM, imag {Key: "PUID", Value: "1000"}, {Key: "PGID", Value: "1000"}, {Key: "LC_ALL", Value: "zh_CN.UTF-8"}, + + // webtop envs: https://github.com/linuxserver/docker-webtop?tab=readme-ov-file#advanced-configuration + // {Key: "DISABLE_SUDO", Value: "true"}, + // Provider // {Key: "MOONSHOT_API_KEY", Value: "abc"}, // {Key: "OPENCLAW_PRIMARY_MODEL", Value: "moonshot/kimi-k2.5"}, @@ -276,12 +319,13 @@ func (c *openclaw) GetContainerSpecs(ctx context.Context, llm *models.SLLM, imag {Key: string(api.LLM_OPENCLAW_CUSTOM_USER), Value: httpAuthUsername}, {Key: string(api.LLM_OPENCLAW_AUTH_PASSWORD), Value: httpAuthPassword}, {Key: string(api.LLM_OPENCLAW_PASSWORD), Value: httpAuthPassword}, + {Key: string(api.LLM_OPENCLAW_CUSTOM_CONFIG), Value: api.LLM_OPENCLAW_CUSTOM_CONFIG_FILE}, // // Browser sidecar // {Key: "BROWSER_CDP_URL", Value: "http://localhost" + ":" + openclawBrowserCDPPort}, // {Key: "BROWSER_DEFAULT_PROFILE", Value: "openclaw"}, // {Key: "BROWSER_EVALUATE_ENABLED", Value: "true"}, // OpenClaw env - {Key: "OPENCLAW_GATEWAY_TOKEN", Value: "abcd"}, + {Key: "OPENCLAW_GATEWAY_TOKEN", Value: llm.GetId()}, {Key: "OPENCLAW_GATEWAY_PORT", Value: "18789"}, {Key: "OPENCLAW_GATEWAY_BIND", Value: "loopback"}, {Key: "OPENCLAW_STATE_DIR", Value: "/config/.openclaw"}, @@ -299,7 +343,7 @@ func (c *openclaw) GetContainerSpecs(ctx context.Context, llm *models.SLLM, imag Index: &diskIndex, SubDirectory: "rootfs", }, - Persistent: true, + Persistent: false, }, } // inject credential envs @@ -320,6 +364,27 @@ func (c *openclaw) GetContainerSpecs(ctx context.Context, llm *models.SLLM, imag for _, channel := range skuSpec.Channels { openclawSpec.Envs = appendCredentialEnvs(openclawSpec.Envs, channel.Credential) } + // inject workspace templates + if skuSpec.WorkspaceTemplates != nil { + if skuSpec.WorkspaceTemplates.AgentsMD != "" { + openclawSpec.Envs = append(openclawSpec.Envs, &commonapi.ContainerKeyValue{ + Key: string(api.LLM_OPENCLAW_TEMPLATE_AGENTS_MD_B64), + Value: base64.StdEncoding.EncodeToString([]byte(skuSpec.WorkspaceTemplates.AgentsMD)), + }) + } + if skuSpec.WorkspaceTemplates.SoulMD != "" { + openclawSpec.Envs = append(openclawSpec.Envs, &commonapi.ContainerKeyValue{ + Key: string(api.LLM_OPENCLAW_TEMPLATE_SOUL_MD_B64), + Value: base64.StdEncoding.EncodeToString([]byte(skuSpec.WorkspaceTemplates.SoulMD)), + }) + } + if skuSpec.WorkspaceTemplates.UserMD != "" { + openclawSpec.Envs = append(openclawSpec.Envs, &commonapi.ContainerKeyValue{ + Key: string(api.LLM_OPENCLAW_TEMPLATE_USER_MD_B64), + Value: base64.StdEncoding.EncodeToString([]byte(skuSpec.WorkspaceTemplates.UserMD)), + }) + } + } return []*computeapi.PodContainerCreateInput{ {