mirror of
https://github.com/sipeed/NanoKVM.git
synced 2026-09-11 00:22:56 -05:00
Merge pull request #798 from BeaconCat/feat/picoclaw-v3-config
feat(picoclaw): adapt to PicoClaw V3 config format
This commit is contained in:
@@ -136,10 +136,15 @@ func loadPicoclawGatewaySettings() (picoclawGatewaySettings, error) {
|
||||
port = defaultPicoclawGatewayPort
|
||||
}
|
||||
|
||||
picoSettings := picoclawPicoSettingsV3{}
|
||||
if pico, ok := cfg.Channels["pico"]; ok {
|
||||
picoSettings = pico.Settings
|
||||
}
|
||||
|
||||
settings := picoclawGatewaySettings{
|
||||
GatewayURL: fmt.Sprintf("ws://%s:%d%s", host, port, picoclawGatewayPath),
|
||||
Token: doc.resolvedGatewayToken(),
|
||||
AllowTokenQuery: cfg.Channels.Pico.AllowTokenQuery,
|
||||
AllowTokenQuery: picoSettings.AllowTokenQuery,
|
||||
}
|
||||
settings.TargetModelName = resolvePicoclawTargetModelName(cfg)
|
||||
|
||||
@@ -148,11 +153,11 @@ func loadPicoclawGatewaySettings() (picoclawGatewaySettings, error) {
|
||||
settings.ModelName = settings.TargetModelName
|
||||
}
|
||||
|
||||
if cfg.Channels.Pico.PingInterval > 0 {
|
||||
settings.PingIntervalMs = cfg.Channels.Pico.PingInterval * 1000
|
||||
if picoSettings.PingInterval > 0 {
|
||||
settings.PingIntervalMs = picoSettings.PingInterval * 1000
|
||||
}
|
||||
if cfg.Channels.Pico.ReadTimeout > 0 {
|
||||
settings.ReadTimeoutMs = cfg.Channels.Pico.ReadTimeout * 1000
|
||||
if picoSettings.ReadTimeout > 0 {
|
||||
settings.ReadTimeoutMs = picoSettings.ReadTimeout * 1000
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
|
||||
@@ -12,11 +12,6 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
picoclawPIDFileName = ".picoclaw.pid"
|
||||
picoclawGatewayTokenPrefix = "pico-"
|
||||
)
|
||||
|
||||
type picoclawConfigFile struct {
|
||||
Agents struct {
|
||||
Defaults struct {
|
||||
@@ -36,15 +31,22 @@ type picoclawConfigFile struct {
|
||||
APIKey string `json:"api_key"`
|
||||
APIKeys []string `json:"api_keys,omitempty"`
|
||||
} `json:"model_list"`
|
||||
Channels struct {
|
||||
Pico struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Token string `json:"token"`
|
||||
AllowTokenQuery bool `json:"allow_token_query"`
|
||||
PingInterval int `json:"ping_interval"`
|
||||
ReadTimeout int `json:"read_timeout"`
|
||||
} `json:"pico"`
|
||||
} `json:"channels"`
|
||||
Channels map[string]picoclawChannelEntry `json:"channel_list"`
|
||||
}
|
||||
|
||||
type picoclawChannelEntry struct {
|
||||
Type string `json:"type"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Settings picoclawPicoSettingsV3 `json:"settings"`
|
||||
}
|
||||
|
||||
type picoclawPicoSettingsV3 struct {
|
||||
Token string `json:"token,omitempty"`
|
||||
AllowTokenQuery bool `json:"allow_token_query,omitempty"`
|
||||
PingInterval int `json:"ping_interval,omitempty"`
|
||||
ReadTimeout int `json:"read_timeout,omitempty"`
|
||||
WriteTimeout int `json:"write_timeout,omitempty"`
|
||||
MaxConnections int `json:"max_connections,omitempty"`
|
||||
}
|
||||
|
||||
type picoclawConfigDocument struct {
|
||||
@@ -154,88 +156,45 @@ func (d *picoclawConfigDocument) saveSecurity() error {
|
||||
}
|
||||
|
||||
type picoclawSecurityConfig struct {
|
||||
ModelList map[string]picoclawModelSecurityEntry `yaml:"model_list,omitempty"`
|
||||
Channels picoclawSecurityChannels `yaml:"channels,omitempty"`
|
||||
ModelList map[string]picoclawModelSecurityEntry `yaml:"model_list,omitempty"`
|
||||
ChannelList map[string]picoclawChannelSecurityEntry `yaml:"channel_list,omitempty"`
|
||||
}
|
||||
|
||||
type picoclawModelSecurityEntry struct {
|
||||
APIKeys []string `yaml:"api_keys,omitempty"`
|
||||
}
|
||||
|
||||
type picoclawSecurityChannels struct {
|
||||
Pico *picoclawPicoSecurity `yaml:"pico,omitempty"`
|
||||
type picoclawChannelSecurityEntry struct {
|
||||
Token string `yaml:"token,omitempty"`
|
||||
Settings *picoclawChannelSecuritySettings `yaml:"settings,omitempty"`
|
||||
}
|
||||
|
||||
type picoclawPicoSecurity struct {
|
||||
type picoclawChannelSecuritySettings struct {
|
||||
Token string `yaml:"token,omitempty"`
|
||||
}
|
||||
|
||||
type picoclawPIDFile struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
func (d *picoclawConfigDocument) resolvedPicoToken() string {
|
||||
if d == nil {
|
||||
return ""
|
||||
}
|
||||
if d.security.Channels.Pico != nil {
|
||||
if token := d.security.Channels.Pico.Token; token != "" {
|
||||
return token
|
||||
// Check security config (channel_list.pico with nested settings)
|
||||
if entry, ok := d.security.ChannelList["pico"]; ok {
|
||||
if entry.Settings != nil && entry.Settings.Token != "" {
|
||||
return entry.Settings.Token
|
||||
}
|
||||
if entry.Token != "" {
|
||||
return entry.Token
|
||||
}
|
||||
}
|
||||
return d.config.Channels.Pico.Token
|
||||
// Fall back to config.json channel_list.pico.settings.token
|
||||
if pico, ok := d.config.Channels["pico"]; ok {
|
||||
return pico.Settings.Token
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (d *picoclawConfigDocument) resolvedGatewayToken() string {
|
||||
baseToken := strings.TrimSpace(d.resolvedPicoToken())
|
||||
if baseToken == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
runtimeToken, err := d.resolvedPIDToken()
|
||||
if err != nil {
|
||||
return baseToken
|
||||
}
|
||||
|
||||
return composePicoclawGatewayToken(baseToken, runtimeToken)
|
||||
}
|
||||
|
||||
func (d *picoclawConfigDocument) resolvedPIDToken() (string, error) {
|
||||
if d == nil || d.configPath == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
pidPath := filepath.Join(filepath.Dir(d.configPath), picoclawPIDFileName)
|
||||
data, err := os.ReadFile(pidPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", nil
|
||||
}
|
||||
return "", fmt.Errorf("failed to read picoclaw pid file: %w", err)
|
||||
}
|
||||
|
||||
var pidFile picoclawPIDFile
|
||||
if err := json.Unmarshal(data, &pidFile); err != nil {
|
||||
return "", fmt.Errorf("failed to parse picoclaw pid file: %w", err)
|
||||
}
|
||||
|
||||
return strings.TrimSpace(pidFile.Token), nil
|
||||
}
|
||||
|
||||
func composePicoclawGatewayToken(baseToken, runtimeToken string) string {
|
||||
baseToken = strings.TrimSpace(baseToken)
|
||||
runtimeToken = strings.TrimSpace(runtimeToken)
|
||||
|
||||
switch {
|
||||
case baseToken == "":
|
||||
return ""
|
||||
case strings.HasPrefix(baseToken, picoclawGatewayTokenPrefix):
|
||||
return baseToken
|
||||
case runtimeToken == "":
|
||||
return baseToken
|
||||
default:
|
||||
return picoclawGatewayTokenPrefix + runtimeToken + baseToken
|
||||
}
|
||||
return strings.TrimSpace(d.resolvedPicoToken())
|
||||
}
|
||||
|
||||
func loadPicoclawSecurityConfig(securityPath string) (picoclawSecurityConfig, error) {
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
const (
|
||||
picoclawBinaryPath = "/usr/bin/picoclaw"
|
||||
picoclawCacheDir = "/root/.picoclaw-cache"
|
||||
picoclawDownloadURL = "https://cdn.sipeed.com/nanokvm/resources/picoclaw/v0.2.6/picoclaw_Linux_riscv64.tar.gz"
|
||||
picoclawChecksumURL = "https://cdn.sipeed.com/nanokvm/resources/picoclaw/v0.2.6/sha512.txt"
|
||||
picoclawDownloadURL = "https://cdn.sipeed.com/nanokvm/resources/picoclaw/v0.2.8/picoclaw_Linux_riscv64.tar.gz"
|
||||
picoclawChecksumURL = "https://cdn.sipeed.com/nanokvm/resources/picoclaw/v0.2.8/sha512.txt"
|
||||
etcInitPicoclawScript = "/etc/init.d/S96picoclaw"
|
||||
kvmappPicoclawScript = "/kvmapp/system/init.d/S96picoclaw"
|
||||
picoclawStartTimeout = 15 * time.Second
|
||||
|
||||
@@ -32,11 +32,12 @@ var picoclawNanoKVMDefaults = []picoclawConfigDefault{
|
||||
{path: []string{"tools", "cron", "allow_command"}, value: true},
|
||||
{path: []string{"tools", "exec", "allow_remote"}, value: true},
|
||||
{path: []string{"tools", "exec", "enable_deny_patterns"}, value: false},
|
||||
{path: []string{"channels", "pico", "allow_token_query"}, value: false},
|
||||
{path: []string{"channels", "pico", "ping_interval"}, value: defaultPicoclawPingSec},
|
||||
{path: []string{"channels", "pico", "read_timeout"}, value: defaultPicoclawReadSec},
|
||||
{path: []string{"channels", "pico", "write_timeout"}, value: defaultPicoclawWriteSec},
|
||||
{path: []string{"channels", "pico", "max_connections"}, value: defaultPicoclawMaxConns},
|
||||
{path: []string{"channel_list", "pico", "type"}, value: "pico"},
|
||||
{path: []string{"channel_list", "pico", "settings", "allow_token_query"}, value: false},
|
||||
{path: []string{"channel_list", "pico", "settings", "ping_interval"}, value: defaultPicoclawPingSec},
|
||||
{path: []string{"channel_list", "pico", "settings", "read_timeout"}, value: defaultPicoclawReadSec},
|
||||
{path: []string{"channel_list", "pico", "settings", "write_timeout"}, value: defaultPicoclawWriteSec},
|
||||
{path: []string{"channel_list", "pico", "settings", "max_connections"}, value: defaultPicoclawMaxConns},
|
||||
{path: []string{"tools", "mcp", "enabled"}, value: true},
|
||||
}
|
||||
|
||||
@@ -72,7 +73,10 @@ func ensurePicoclawStartupDefaults() error {
|
||||
}
|
||||
|
||||
func ensurePicoclawPicoChannelEnabled(doc *picoclawConfigDocument) error {
|
||||
if doc == nil || doc.config.Channels.Pico.Enabled {
|
||||
if doc == nil {
|
||||
return nil
|
||||
}
|
||||
if pico, ok := doc.config.Channels["pico"]; ok && pico.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -98,12 +102,20 @@ func applyPicoclawStartupDefaults(editor *picoclawConfigEditor) error {
|
||||
}
|
||||
|
||||
func forceEnablePicoclawPicoChannel(doc *picoclawConfigDocument, editor *picoclawConfigEditor) {
|
||||
if doc == nil || editor == nil || doc.config.Channels.Pico.Enabled {
|
||||
if doc == nil || editor == nil {
|
||||
return
|
||||
}
|
||||
if pico, ok := doc.config.Channels["pico"]; ok && pico.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
editor.setValue(true, "channels", "pico", "enabled")
|
||||
doc.config.Channels.Pico.Enabled = true
|
||||
editor.setValue(true, "channel_list", "pico", "enabled")
|
||||
if doc.config.Channels == nil {
|
||||
doc.config.Channels = make(map[string]picoclawChannelEntry)
|
||||
}
|
||||
pico := doc.config.Channels["pico"]
|
||||
pico.Enabled = true
|
||||
doc.config.Channels["pico"] = pico
|
||||
}
|
||||
|
||||
func defaultPicoclawMCPServer() (map[string]any, error) {
|
||||
@@ -197,14 +209,19 @@ func ensurePicoclawPicoToken(doc *picoclawConfigDocument) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if doc.security.Channels.Pico == nil {
|
||||
doc.security.Channels.Pico = &picoclawPicoSecurity{}
|
||||
if doc.security.ChannelList == nil {
|
||||
doc.security.ChannelList = make(map[string]picoclawChannelSecurityEntry)
|
||||
}
|
||||
if strings.TrimSpace(doc.security.Channels.Pico.Token) == token {
|
||||
entry := doc.security.ChannelList["pico"]
|
||||
if entry.Settings == nil {
|
||||
entry.Settings = &picoclawChannelSecuritySettings{}
|
||||
}
|
||||
if strings.TrimSpace(entry.Settings.Token) == token {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
doc.security.Channels.Pico.Token = token
|
||||
entry.Settings.Token = token
|
||||
doc.security.ChannelList["pico"] = entry
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -213,14 +230,23 @@ func picoSecurityToken(doc *picoclawConfigDocument) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if doc.security.Channels.Pico != nil {
|
||||
if token := strings.TrimSpace(doc.security.Channels.Pico.Token); token != "" {
|
||||
// Check security channel_list.pico.settings.token
|
||||
if entry, ok := doc.security.ChannelList["pico"]; ok {
|
||||
if entry.Settings != nil {
|
||||
if token := strings.TrimSpace(entry.Settings.Token); token != "" {
|
||||
return token, nil
|
||||
}
|
||||
}
|
||||
if token := strings.TrimSpace(entry.Token); token != "" {
|
||||
return token, nil
|
||||
}
|
||||
}
|
||||
|
||||
if token := strings.TrimSpace(doc.config.Channels.Pico.Token); token != "" {
|
||||
return token, nil
|
||||
// Fall back to config channel_list.pico.settings.token
|
||||
if pico, ok := doc.config.Channels["pico"]; ok {
|
||||
if token := strings.TrimSpace(pico.Settings.Token); token != "" {
|
||||
return token, nil
|
||||
}
|
||||
}
|
||||
|
||||
return generatePicoclawToken()
|
||||
|
||||
Reference in New Issue
Block a user