Files
picoclaw/web/backend/utils/runtime.go
T
2026-04-14 14:04:37 +08:00

160 lines
3.7 KiB
Go

package utils
import (
"fmt"
"net"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/logger"
)
// GetPicoclawHome returns the picoclaw home directory.
// Priority: $PICOCLAW_HOME > ~/.picoclaw
func GetPicoclawHome() string {
return config.GetHome()
}
// GetDefaultConfigPath returns the default path to the picoclaw config file.
func GetDefaultConfigPath() string {
if configPath := os.Getenv(config.EnvConfig); configPath != "" {
return configPath
}
return filepath.Join(GetPicoclawHome(), "config.json")
}
// FindPicoclawBinary locates the picoclaw executable.
// Search order:
// 1. PICOCLAW_BINARY environment variable (explicit override)
// 2. Same directory as the current executable
// 3. Falls back to "picoclaw" and relies on $PATH
func FindPicoclawBinary() string {
binaryName := "picoclaw"
if runtime.GOOS == "windows" {
binaryName = "picoclaw.exe"
}
if p := os.Getenv(config.EnvBinary); p != "" {
if info, _ := os.Stat(p); info != nil && !info.IsDir() {
return p
}
}
if exe, err := os.Executable(); err == nil {
logger.Debugf("Trying to find picoclaw binary in %s", exe)
candidate := filepath.Join(filepath.Dir(exe), binaryName)
if info, err := os.Stat(candidate); err == nil && !info.IsDir() {
return candidate
}
}
return "picoclaw"
}
func appendUniqueIP(addrs []string, seen map[string]struct{}, value string) []string {
value = strings.TrimSpace(value)
if value == "" {
return addrs
}
if _, ok := seen[value]; ok {
return addrs
}
seen[value] = struct{}{}
return append(addrs, value)
}
// GetLocalIPv4s returns all non-loopback local IPv4 addresses.
func GetLocalIPv4s() []string {
addrs, err := net.InterfaceAddrs()
if err != nil {
return nil
}
results := make([]string, 0, 4)
seen := make(map[string]struct{}, 4)
for _, a := range addrs {
ipnet, ok := a.(*net.IPNet)
if !ok || ipnet.IP == nil || ipnet.IP.IsLoopback() {
continue
}
if ip4 := ipnet.IP.To4(); ip4 != nil {
results = appendUniqueIP(results, seen, ip4.String())
}
}
return results
}
func isDisplayGlobalIPv6(ip net.IP) bool {
if ip == nil || ip.IsLoopback() || ip.To4() != nil {
return false
}
ip = ip.To16()
if ip == nil {
return false
}
// Only show IPv6 global unicast addresses in 2000::/3.
return ip[0]&0xe0 == 0x20
}
// GetGlobalIPv6s returns all IPv6 global unicast addresses.
func GetGlobalIPv6s() []string {
addrs, err := net.InterfaceAddrs()
if err != nil {
return nil
}
results := make([]string, 0, 4)
seen := make(map[string]struct{}, 4)
for _, a := range addrs {
ipnet, ok := a.(*net.IPNet)
if !ok || ipnet.IP == nil {
continue
}
ip := ipnet.IP
if !isDisplayGlobalIPv6(ip) {
continue
}
results = appendUniqueIP(results, seen, ip.String())
}
return results
}
// GetLocalIPv4 returns the first non-loopback local IPv4 address.
func GetLocalIPv4() string {
addrs := GetLocalIPv4s()
if len(addrs) == 0 {
return ""
}
return addrs[0]
}
// GetLocalIPv6 returns the first IPv6 global unicast address.
func GetLocalIPv6() string {
addrs := GetGlobalIPv6s()
if len(addrs) == 0 {
return ""
}
return addrs[0]
}
// GetLocalIP returns a non-loopback local IPv4 address for backward compatibility.
func GetLocalIP() string {
return GetLocalIPv4()
}
// OpenBrowser automatically opens the given URL in the default browser.
func OpenBrowser(url string) error {
switch runtime.GOOS {
case "linux":
return exec.Command("xdg-open", url).Start()
case "windows":
return exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
case "darwin":
return exec.Command("open", url).Start()
default:
return fmt.Errorf("unsupported platform")
}
}