Files
wehub-resource-sync 6d978fe483
Build / Frontend Build (push) Has been cancelled
Build / Backend Build (push) Has been cancelled
Build / Docker Build (push) Has been cancelled
Build / Kubernetes Deploy Smoke Test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:24 +08:00

337 lines
10 KiB
Go

package services
import (
"fmt"
"os"
"strings"
"clawreef/internal/services/k8s"
)
// InstanceRuntimeConfig describes how a given instance type runs inside Kubernetes.
type InstanceRuntimeConfig struct {
Image string
Port int32
MountPath string
Env map[string]string
}
const (
kasmClipboardSendDisabled = "-SendCutText 0"
kasmClipboardAcceptDisabled = "-AcceptCutText 0"
)
func buildRuntimeConfig(instanceType, osType, osVersion string, registry, tag *string) InstanceRuntimeConfig {
if registry != nil && strings.TrimSpace(*registry) != "" && (tag == nil || strings.TrimSpace(*tag) == "") {
return InstanceRuntimeConfig{
Image: strings.TrimSpace(*registry),
Port: defaultPortForInstanceType(instanceType),
MountPath: defaultMountPathForInstanceType(instanceType),
Env: defaultEnvForInstanceType(instanceType),
}
}
defaultRegistry := "docker.io/clawreef"
if registry != nil && *registry != "" {
defaultRegistry = *registry
}
defaultTag := osVersion
if tag != nil && *tag != "" {
defaultTag = *tag
}
config := InstanceRuntimeConfig{
Port: 3001,
MountPath: "/home/user/data",
Env: map[string]string{},
}
switch instanceType {
case "ubuntu":
config.Image = "lscr.io/linuxserver/webtop:ubuntu-xfce"
config.Port = 3001
config.MountPath = "/config"
config.Env = defaultWebtopDesktopEnv("ClawManager Desktop")
case "webtop":
config.Image = "lscr.io/linuxserver/webtop:ubuntu-xfce"
config.Port = 3001
config.MountPath = "/config"
config.Env = defaultWebtopDesktopEnv("ClawManager Webtop")
case "hermes":
config.Image = defaultSystemImageSettings["hermes"]
config.Port = 3001
config.MountPath = "/config"
config.Env = defaultWebtopDesktopEnv("Hermes Runtime")
config.Env["HERMES_HOME"] = "/config/.hermes"
case "openclaw":
config.MountPath = "/config"
if (registry == nil || strings.TrimSpace(*registry) == "") && (tag == nil || strings.TrimSpace(*tag) == "") {
config.Image = defaultSystemImageSettings["openclaw"]
} else {
config.Image = fmt.Sprintf("%s/%s:%s", defaultRegistry, "openclaw-desktop", defaultTag)
}
config.Env = defaultWebtopDesktopEnv("ClawManager Desktop")
case "debian":
config.Image = fmt.Sprintf("%s/%s:%s", defaultRegistry, "debian-desktop", defaultTag)
case "centos":
config.Image = fmt.Sprintf("%s/%s:%s", defaultRegistry, "centos-desktop", defaultTag)
default:
config.Image = fmt.Sprintf("%s/%s:%s", defaultRegistry, fmt.Sprintf("%s-desktop", osType), defaultTag)
}
return config
}
func defaultPortForInstanceType(instanceType string) int32 {
switch instanceType {
case "ubuntu", "webtop", "hermes":
return 3001
default:
return 3001
}
}
func defaultMountPathForInstanceType(instanceType string) string {
switch instanceType {
case "ubuntu", "webtop", "openclaw":
return "/config"
case "hermes":
return "/config"
default:
return "/home/user/data"
}
}
func defaultEnvForInstanceType(instanceType string) map[string]string {
switch instanceType {
case "ubuntu", "webtop", "openclaw":
return defaultWebtopDesktopEnv("ClawManager Desktop")
case "hermes":
env := defaultWebtopDesktopEnv("Hermes Runtime")
env["HERMES_HOME"] = "/config/.hermes"
return env
default:
return map[string]string{}
}
}
func defaultWebtopDesktopEnv(title string) map[string]string {
return map[string]string{
"TITLE": title,
"SUBFOLDER": "/",
"KASM_SVC_SEND_CUT_TEXT": kasmClipboardSendDisabled,
"KASM_SVC_ACCEPT_CUT_TEXT": kasmClipboardAcceptDisabled,
}
}
func withInstanceProxyEnv(instanceType string, instanceID int, env map[string]string) map[string]string {
merged := map[string]string{}
for key, value := range env {
merged[key] = value
}
if proxyURL, ok := defaultEgressProxyURL(); ok {
noProxy := defaultNoProxyList()
merged["HTTP_PROXY"] = proxyURL
merged["HTTPS_PROXY"] = proxyURL
merged["http_proxy"] = proxyURL
merged["https_proxy"] = proxyURL
merged["NO_PROXY"] = noProxy
merged["no_proxy"] = noProxy
}
if usesWebtopImage(instanceType) {
merged["SUBFOLDER"] = fmt.Sprintf("/api/v1/instances/%d/proxy/", instanceID)
}
return merged
}
func usesWebtopImage(instanceType string) bool {
switch instanceType {
case "ubuntu", "webtop", "hermes", "openclaw":
return true
default:
return false
}
}
// defaultImagePullPolicy returns the image pull policy to use for instance
// pods. Managed runtime instances always use IfNotPresent so local caches can
// be reused without forcing a remote registry pull during create/start flows.
func defaultImagePullPolicy() string {
return "IfNotPresent"
}
func defaultEgressProxyURL() (string, bool) {
if override := strings.TrimSpace(os.Getenv("CLAWMANAGER_EGRESS_PROXY_URL")); override != "" {
return override, true
}
client := k8s.GetClient()
var systemNamespace string
if overrideNamespace := strings.TrimSpace(os.Getenv("CLAWMANAGER_SYSTEM_NAMESPACE")); overrideNamespace != "" {
systemNamespace = overrideNamespace
} else if client != nil {
systemNamespace = fmt.Sprintf("%s-system", client.Namespace)
} else if baseNamespace := strings.TrimSpace(os.Getenv("K8S_NAMESPACE")); baseNamespace != "" {
systemNamespace = fmt.Sprintf("%s-system", baseNamespace)
}
if strings.TrimSpace(systemNamespace) == "" {
return "", false
}
serviceName := strings.TrimSpace(os.Getenv("CLAWMANAGER_EGRESS_PROXY_SERVICE_NAME"))
if serviceName == "" {
serviceName = strings.TrimSpace(os.Getenv("CLAWMANAGER_EGRESS_PROXY_SERVICE"))
}
if serviceName == "" {
serviceName = "clawmanager-egress-proxy"
}
port := normalizePortValue(
strings.TrimSpace(os.Getenv("CLAWMANAGER_EGRESS_PROXY_SERVICE_PORT")),
strings.TrimSpace(os.Getenv("CLAWMANAGER_EGRESS_PROXY_PORT")),
)
if port == "" {
port = "3128"
}
return fmt.Sprintf("http://%s.%s.svc.cluster.local:%s", serviceName, systemNamespace, port), true
}
func defaultGatewayBaseURL() (string, bool) {
if override := strings.TrimSpace(os.Getenv("CLAWMANAGER_LLM_GATEWAY_BASE_URL")); override != "" {
return override, true
}
systemNamespace := strings.TrimSpace(os.Getenv("CLAWMANAGER_SYSTEM_NAMESPACE"))
if systemNamespace == "" {
if client := k8s.GetClient(); client != nil {
systemNamespace = client.GetSystemNamespace()
} else if baseNamespace := strings.TrimSpace(os.Getenv("K8S_NAMESPACE")); baseNamespace != "" {
systemNamespace = fmt.Sprintf("%s-system", baseNamespace)
}
}
if systemNamespace == "" {
return "", false
}
serviceName := strings.TrimSpace(os.Getenv("CLAWMANAGER_LLM_GATEWAY_SERVICE_NAME"))
if serviceName == "" {
serviceName = strings.TrimSpace(os.Getenv("CLAWMANAGER_LLM_GATEWAY_SERVICE"))
}
if serviceName == "" {
serviceName = "clawmanager-gateway"
}
port := normalizePortValue(
strings.TrimSpace(os.Getenv("CLAWMANAGER_LLM_GATEWAY_SERVICE_PORT")),
strings.TrimSpace(os.Getenv("CLAWMANAGER_LLM_GATEWAY_PORT")),
)
if port == "" {
port = "9001"
}
return fmt.Sprintf("http://%s.%s.svc.cluster.local:%s/api/v1/gateway/llm", serviceName, systemNamespace, port), true
}
func defaultAgentControlBaseURL() (string, bool) {
if override := strings.TrimSpace(os.Getenv("CLAWMANAGER_AGENT_CONTROL_BASE_URL")); override != "" {
return override, true
}
systemNamespace := strings.TrimSpace(os.Getenv("CLAWMANAGER_SYSTEM_NAMESPACE"))
if systemNamespace == "" {
if client := k8s.GetClient(); client != nil {
systemNamespace = client.GetSystemNamespace()
} else if baseNamespace := strings.TrimSpace(os.Getenv("K8S_NAMESPACE")); baseNamespace != "" {
systemNamespace = fmt.Sprintf("%s-system", baseNamespace)
}
}
if systemNamespace == "" {
return "", false
}
serviceName := strings.TrimSpace(os.Getenv("CLAWMANAGER_AGENT_CONTROL_SERVICE_NAME"))
if serviceName == "" {
serviceName = strings.TrimSpace(os.Getenv("CLAWMANAGER_LLM_GATEWAY_SERVICE_NAME"))
}
if serviceName == "" {
serviceName = strings.TrimSpace(os.Getenv("CLAWMANAGER_LLM_GATEWAY_SERVICE"))
}
if serviceName == "" {
serviceName = "clawmanager-gateway"
}
port := normalizePortValue(
strings.TrimSpace(os.Getenv("CLAWMANAGER_AGENT_CONTROL_SERVICE_PORT")),
strings.TrimSpace(os.Getenv("CLAWMANAGER_LLM_GATEWAY_SERVICE_PORT")),
strings.TrimSpace(os.Getenv("CLAWMANAGER_LLM_GATEWAY_PORT")),
)
if port == "" {
port = "9001"
}
return fmt.Sprintf("http://%s.%s.svc.cluster.local:%s", serviceName, systemNamespace, port), true
}
func defaultNoProxyList() string {
if override := strings.TrimSpace(os.Getenv("CLAWMANAGER_NO_PROXY")); override != "" {
return override
}
systemNamespace := strings.TrimSpace(os.Getenv("CLAWMANAGER_SYSTEM_NAMESPACE"))
if systemNamespace == "" {
if client := k8s.GetClient(); client != nil {
systemNamespace = fmt.Sprintf("%s-system", client.Namespace)
} else if baseNamespace := strings.TrimSpace(os.Getenv("K8S_NAMESPACE")); baseNamespace != "" {
systemNamespace = fmt.Sprintf("%s-system", baseNamespace)
}
}
serviceNames := []string{
"localhost",
"127.0.0.1",
"clawmanager-frontend",
"clawmanager-gateway",
"clawmanager-egress-proxy",
}
if systemNamespace != "" {
serviceNames = append(serviceNames,
fmt.Sprintf("clawmanager-frontend.%s", systemNamespace),
fmt.Sprintf("clawmanager-frontend.%s.svc", systemNamespace),
fmt.Sprintf("clawmanager-frontend.%s.svc.cluster.local", systemNamespace),
fmt.Sprintf("clawmanager-gateway.%s", systemNamespace),
fmt.Sprintf("clawmanager-gateway.%s.svc", systemNamespace),
fmt.Sprintf("clawmanager-gateway.%s.svc.cluster.local", systemNamespace),
fmt.Sprintf("clawmanager-egress-proxy.%s", systemNamespace),
fmt.Sprintf("clawmanager-egress-proxy.%s.svc", systemNamespace),
fmt.Sprintf("clawmanager-egress-proxy.%s.svc.cluster.local", systemNamespace),
)
}
return strings.Join(serviceNames, ",")
}
func normalizePortValue(values ...string) string {
for _, raw := range values {
value := strings.TrimSpace(raw)
if value == "" {
continue
}
if !strings.Contains(value, "://") {
return value
}
lastColon := strings.LastIndex(value, ":")
if lastColon >= 0 && lastColon < len(value)-1 {
return value[lastColon+1:]
}
}
return ""
}