项目文件夹

文件
2026-05-22 19:21:11 +08:00

378 行
11 KiB
Go

package services
import (
"errors"
"fmt"
"strings"
"clawreef/internal/models"
"clawreef/internal/repository"
)
var orderedSystemImageTypes = []string{
"openclaw",
"ubuntu",
"webtop",
"hermes",
"debian",
"centos",
"custom",
}
var supportedSystemImageTypes = map[string]string{
"openclaw": "OpenClaw Desktop",
"ubuntu": "Ubuntu Desktop",
"webtop": "Webtop Desktop",
"hermes": "Hermes Runtime",
"debian": "Debian Desktop",
"centos": "CentOS Desktop",
"custom": "Custom Image",
}
var defaultSystemImageSettings = map[string]string{
"openclaw": "ghcr.io/yuan-lab-llm/agentsruntime/openclaw:latest",
"ubuntu": "lscr.io/linuxserver/webtop:ubuntu-xfce",
"webtop": "lscr.io/linuxserver/webtop:ubuntu-xfce",
"hermes": "ghcr.io/yuan-lab-llm/agentsruntime/hermes:latest",
"debian": "docker.io/clawreef/debian-desktop:12",
"centos": "docker.io/clawreef/centos-desktop:9",
"custom": "registry.example.com/your-custom-image:latest",
}
var defaultShellSystemImageSettings = map[string]string{
"openclaw": "ghcr.io/yuan-lab-llm/agentsruntime/openclaw-shell:latest",
"ubuntu": "ubuntu:22.04",
"webtop": "ubuntu:22.04",
"hermes": "ghcr.io/yuan-lab-llm/agentsruntime/hermes-shell:latest",
"debian": "debian:12",
"centos": "quay.io/centos/centos:stream9",
"custom": "registry.example.com/your-custom-shell-image:latest",
}
var defaultEnabledSystemImageTypes = map[string]bool{
"openclaw": true,
"ubuntu": true,
"hermes": true,
}
var defaultEnabledShellSystemImageTypes = map[string]bool{
"openclaw": true,
}
// RuntimeImageConfig is the runtime card selected for an instance type.
type RuntimeImageConfig struct {
Image string
RuntimeType string
}
// RuntimeImageSettingsProvider exposes runtime image lookup for instance types.
type RuntimeImageSettingsProvider interface {
GetRuntimeImage(instanceType string) (RuntimeImageConfig, bool)
GetRuntimeImageForImage(instanceType, image string) (RuntimeImageConfig, bool)
}
var runtimeImageSettingsProvider RuntimeImageSettingsProvider
// SetRuntimeImageSettingsProvider configures the global runtime image provider used by runtime resolution.
func SetRuntimeImageSettingsProvider(provider RuntimeImageSettingsProvider) {
runtimeImageSettingsProvider = provider
}
type SystemImageSettingService interface {
List() ([]models.SystemImageSetting, error)
Save(setting *models.SystemImageSetting) (*models.SystemImageSetting, error)
DeleteByID(id int) error
DisableType(instanceType string) error
GetRuntimeImage(instanceType string) (RuntimeImageConfig, bool)
GetRuntimeImageForImage(instanceType, image string) (RuntimeImageConfig, bool)
}
type systemImageSettingService struct {
repo repository.SystemImageSettingRepository
}
// NewSystemImageSettingService creates a new system image setting service.
func NewSystemImageSettingService(repo repository.SystemImageSettingRepository) SystemImageSettingService {
return &systemImageSettingService{repo: repo}
}
func (s *systemImageSettingService) List() ([]models.SystemImageSetting, error) {
stored, err := s.repo.List()
if err != nil {
return nil, err
}
byType := make(map[string][]models.SystemImageSetting, len(orderedSystemImageTypes))
for _, item := range stored {
normalizedType := strings.TrimSpace(strings.ToLower(item.InstanceType))
item.InstanceType = normalizedType
item.RuntimeType = normalizeSystemImageRuntimeType(item.RuntimeType)
if strings.TrimSpace(item.DisplayName) == "" {
item.DisplayName = displayNameForSystemImageType(normalizedType)
}
byType[normalizedType] = append(byType[normalizedType], item)
}
settings := make([]models.SystemImageSetting, 0, len(stored)+len(orderedSystemImageTypes))
for _, instanceType := range orderedSystemImageTypes {
items := byType[instanceType]
if len(items) == 0 {
settings = append(settings, defaultSystemImagePresetsForType(instanceType)...)
continue
}
for _, item := range items {
if item.IsEnabled {
settings = append(settings, item)
}
}
delete(byType, instanceType)
}
for instanceType, items := range byType {
for _, item := range items {
if item.IsEnabled {
if strings.TrimSpace(item.DisplayName) == "" {
item.DisplayName = displayNameForSystemImageType(instanceType)
}
settings = append(settings, item)
}
}
}
return settings, nil
}
func (s *systemImageSettingService) Save(setting *models.SystemImageSetting) (*models.SystemImageSetting, error) {
normalizedType := strings.TrimSpace(strings.ToLower(setting.InstanceType))
if _, ok := supportedSystemImageTypes[normalizedType]; !ok {
return nil, errors.New("unsupported instance type")
}
runtimeType, err := validateSystemImageRuntimeType(setting.RuntimeType)
if err != nil {
return nil, err
}
image := strings.TrimSpace(setting.Image)
if image == "" {
return nil, errors.New("image is required")
}
setting.InstanceType = normalizedType
setting.RuntimeType = runtimeType
setting.Image = image
setting.DisplayName = strings.TrimSpace(setting.DisplayName)
if setting.DisplayName == "" {
setting.DisplayName = displayNameForSystemImagePreset(normalizedType, runtimeType)
}
setting.IsEnabled = true
if err := s.repo.Save(setting); err != nil {
return nil, err
}
return setting, nil
}
func (s *systemImageSettingService) DeleteByID(id int) error {
if id <= 0 {
return errors.New("invalid image setting id")
}
existing, err := s.repo.GetByID(id)
if err != nil {
return err
}
if existing == nil {
return nil
}
if err := s.repo.DeleteByID(id); err != nil {
return err
}
remaining, err := s.repo.ListByInstanceType(existing.InstanceType)
if err != nil {
return err
}
if len(remaining) > 0 || !isSupportedSystemImageType(existing.InstanceType) {
return nil
}
return s.disableTypeWithFallback(existing.InstanceType)
}
func (s *systemImageSettingService) DisableType(instanceType string) error {
normalizedType := strings.TrimSpace(strings.ToLower(instanceType))
if !isSupportedSystemImageType(normalizedType) {
return errors.New("unsupported instance type")
}
return s.disableTypeWithFallback(normalizedType)
}
func (s *systemImageSettingService) disableTypeWithFallback(instanceType string) error {
if err := s.repo.DeleteByInstanceType(instanceType); err != nil {
return err
}
return s.repo.Save(&models.SystemImageSetting{
InstanceType: instanceType,
RuntimeType: "desktop",
DisplayName: displayNameForSystemImagePreset(instanceType, "desktop"),
Image: defaultSystemImageSettings[instanceType],
IsEnabled: false,
})
}
func (s *systemImageSettingService) GetRuntimeImage(instanceType string) (RuntimeImageConfig, bool) {
normalizedType := strings.TrimSpace(strings.ToLower(instanceType))
items, err := s.repo.ListByInstanceType(normalizedType)
if err != nil {
return RuntimeImageConfig{}, false
}
if len(items) == 0 {
for _, item := range defaultSystemImagePresetsForType(normalizedType) {
image := strings.TrimSpace(item.Image)
if item.IsEnabled && image != "" {
return RuntimeImageConfig{Image: image, RuntimeType: item.RuntimeType}, true
}
}
return RuntimeImageConfig{}, false
}
for _, item := range items {
if !item.IsEnabled {
continue
}
image := strings.TrimSpace(item.Image)
if image != "" {
return RuntimeImageConfig{
Image: image,
RuntimeType: normalizeSystemImageRuntimeType(item.RuntimeType),
}, true
}
}
return RuntimeImageConfig{}, false
}
func (s *systemImageSettingService) GetRuntimeImageForImage(instanceType, image string) (RuntimeImageConfig, bool) {
normalizedType := strings.TrimSpace(strings.ToLower(instanceType))
normalizedImage := strings.TrimSpace(image)
if normalizedType == "" || normalizedImage == "" {
return RuntimeImageConfig{}, false
}
items, err := s.repo.ListByInstanceType(normalizedType)
if err != nil {
return RuntimeImageConfig{}, false
}
if len(items) == 0 {
for _, item := range defaultSystemImagePresetsForType(normalizedType) {
defaultImage := strings.TrimSpace(item.Image)
if item.IsEnabled && defaultImage == normalizedImage {
return RuntimeImageConfig{Image: defaultImage, RuntimeType: item.RuntimeType}, true
}
}
return RuntimeImageConfig{}, false
}
for _, item := range items {
if !item.IsEnabled {
continue
}
if strings.TrimSpace(item.Image) == normalizedImage {
return RuntimeImageConfig{
Image: normalizedImage,
RuntimeType: normalizeSystemImageRuntimeType(item.RuntimeType),
}, true
}
}
return RuntimeImageConfig{}, false
}
func runtimeImageOverride(instanceType string) (RuntimeImageConfig, bool) {
if runtimeImageSettingsProvider == nil {
return RuntimeImageConfig{}, false
}
return runtimeImageSettingsProvider.GetRuntimeImage(instanceType)
}
func runtimeImageOverrideForImage(instanceType, image string) (RuntimeImageConfig, bool) {
if runtimeImageSettingsProvider == nil {
return RuntimeImageConfig{}, false
}
return runtimeImageSettingsProvider.GetRuntimeImageForImage(instanceType, image)
}
func displayNameForSystemImageType(instanceType string) string {
if name, ok := supportedSystemImageTypes[instanceType]; ok {
return name
}
return fmt.Sprintf("%s Image", instanceType)
}
func displayNameForSystemImagePreset(instanceType, runtimeType string) string {
normalizedRuntimeType := normalizeSystemImageRuntimeType(runtimeType)
if instanceType == "openclaw" {
if normalizedRuntimeType == "shell" {
return "OpenClaw Shell"
}
return "OpenClaw Desktop"
}
return displayNameForSystemImageType(instanceType)
}
func defaultSystemImagePresetsForType(instanceType string) []models.SystemImageSetting {
settings := []models.SystemImageSetting{{
InstanceType: instanceType,
RuntimeType: "desktop",
DisplayName: displayNameForSystemImagePreset(instanceType, "desktop"),
Image: defaultSystemImageSettings[instanceType],
IsEnabled: defaultEnabledSystemImageTypes[instanceType],
}}
if image := strings.TrimSpace(defaultShellSystemImageSettings[instanceType]); image != "" {
if defaultEnabledShellSystemImageTypes[instanceType] {
settings = append(settings, models.SystemImageSetting{
InstanceType: instanceType,
RuntimeType: "shell",
DisplayName: displayNameForSystemImagePreset(instanceType, "shell"),
Image: image,
IsEnabled: true,
})
}
}
return settings
}
func isSupportedSystemImageType(instanceType string) bool {
_, ok := supportedSystemImageTypes[instanceType]
return ok
}
func normalizeSystemImageRuntimeType(runtimeType string) string {
normalized := strings.TrimSpace(strings.ToLower(runtimeType))
if normalized == "shell" {
return "shell"
}
return "desktop"
}
func validateSystemImageRuntimeType(runtimeType string) (string, error) {
normalized := strings.TrimSpace(strings.ToLower(runtimeType))
if normalized == "" {
return "desktop", nil
}
if normalized != "desktop" && normalized != "shell" {
return "", errors.New("unsupported runtime type")
}
return normalized, nil
}