Improve remote endpoint detection

This commit is contained in:
lutc5
2026-04-30 12:57:47 +08:00
parent 2bcb0a6715
commit e88856e1fc
13 changed files with 628 additions and 50 deletions

View File

@@ -9,6 +9,7 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
@@ -62,22 +63,33 @@ func loadCredentialFile(path string) (Credential, error) {
}
func importLingmaCacheCredential() (Credential, error) {
home, err := os.UserHomeDir()
if err != nil {
return Credential{}, err
var attempts []string
for _, lingmaDir := range candidateLingmaCacheDirs() {
cred, err := importLingmaCacheCredentialFromDir(lingmaDir)
if err == nil {
return cred, nil
}
attempts = append(attempts, fmt.Sprintf("%s: %v", lingmaDir, err))
}
lingmaDir := filepath.Join(home, ".lingma")
if len(attempts) == 0 {
return Credential{}, errors.New("no Lingma cache directory candidate was found")
}
return Credential{}, fmt.Errorf("load Lingma login cache: %s", strings.Join(attempts, "; "))
}
func importLingmaCacheCredentialFromDir(lingmaDir string) (Credential, error) {
machineID, err := loadMachineID(lingmaDir)
if err != nil {
return Credential{}, err
}
encrypted, err := os.ReadFile(filepath.Join(lingmaDir, "cache", "user"))
userPath := filepath.Join(lingmaDir, "cache", "user")
encrypted, err := os.ReadFile(userPath)
if err != nil {
return Credential{}, fmt.Errorf("read ~/.lingma/cache/user: %w", err)
return Credential{}, fmt.Errorf("read %s: %w", userPath, err)
}
ciphertext, err := base64.StdEncoding.DecodeString(strings.TrimSpace(string(encrypted)))
if err != nil {
return Credential{}, fmt.Errorf("decode ~/.lingma/cache/user: %w", err)
return Credential{}, fmt.Errorf("decode %s: %w", userPath, err)
}
plaintext, err := decryptCacheUser(machineID, ciphertext)
if err != nil {
@@ -90,19 +102,46 @@ func importLingmaCacheCredential() (Credential, error) {
ExpireTime any `json:"expire_time"`
}
if err := json.Unmarshal(plaintext, &payload); err != nil {
return Credential{}, fmt.Errorf("parse ~/.lingma/cache/user: %w", err)
return Credential{}, fmt.Errorf("parse %s: %w", userPath, err)
}
cred := Credential{
CosyKey: payload.Key,
EncryptUserInfo: payload.EncryptUserInfo,
UserID: payload.UserID,
MachineID: machineID,
Source: "~/.lingma/cache/user",
Source: userPath,
TokenExpireTime: parseExpireAny(payload.ExpireTime),
}
return cred, validateCredential(cred)
}
func candidateLingmaCacheDirs() []string {
if explicit := strings.TrimSpace(os.Getenv("LINGMA_CACHE_DIR")); explicit != "" {
return []string{expandHome(explicit)}
}
var dirs []string
if home, err := os.UserHomeDir(); err == nil && strings.TrimSpace(home) != "" {
dirs = append(dirs,
filepath.Join(home, ".lingma"),
filepath.Join(home, ".config", "Lingma"),
filepath.Join(home, ".local", "share", "Lingma"),
)
}
for _, envName := range []string{"APPDATA", "LOCALAPPDATA", "ProgramData"} {
if value := strings.TrimSpace(os.Getenv(envName)); value != "" {
dirs = append(dirs,
filepath.Join(value, "Lingma"),
filepath.Join(value, "lingma"),
)
}
}
if value := strings.TrimSpace(os.Getenv("XDG_CONFIG_HOME")); value != "" {
dirs = append(dirs, filepath.Join(value, "Lingma"))
}
return uniquePathStrings(dirs)
}
func loadMachineID(lingmaDir string) (string, error) {
if body, err := os.ReadFile(filepath.Join(lingmaDir, "cache", "id")); err == nil {
if value := strings.TrimSpace(string(body)); value != "" {
@@ -111,7 +150,7 @@ func loadMachineID(lingmaDir string) (string, error) {
}
logBody, err := os.ReadFile(filepath.Join(lingmaDir, "logs", "lingma.log"))
if err != nil {
return "", fmt.Errorf("remote credential requires ~/.lingma/cache/id or lingma.log machine id: %w", err)
return "", fmt.Errorf("remote credential requires cache/id or lingma.log machine id: %w", err)
}
markers := []string{"using machine id from file:", "machine id:"}
text := string(logBody)
@@ -128,7 +167,7 @@ func loadMachineID(lingmaDir string) (string, error) {
return value, nil
}
}
return "", errors.New("machine id not found in ~/.lingma cache")
return "", errors.New("machine id not found in Lingma cache")
}
func decryptCacheUser(machineID string, ciphertext []byte) ([]byte, error) {
@@ -203,3 +242,44 @@ func parseExpireAny(value any) int64 {
func IsExpired(cred Credential, margin time.Duration) bool {
return cred.TokenExpireTime > 0 && time.Now().Add(margin).UnixMilli() > cred.TokenExpireTime
}
func MachineOSHeader() string {
switch runtime.GOOS {
case "darwin":
if runtime.GOARCH == "arm64" {
return "arm64_darwin"
}
return "x86_64_darwin"
case "windows":
if runtime.GOARCH == "arm64" {
return "arm64_windows"
}
return "x86_64_windows"
case "linux":
if runtime.GOARCH == "arm64" {
return "arm64_linux"
}
return "x86_64_linux"
default:
return runtime.GOARCH + "_" + runtime.GOOS
}
}
func uniquePathStrings(values []string) []string {
seen := make(map[string]struct{}, len(values))
out := make([]string, 0, len(values))
for _, value := range values {
value = strings.TrimSpace(value)
if value == "" {
continue
}
cleaned := filepath.Clean(value)
key := strings.ToLower(cleaned)
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
out = append(out, cleaned)
}
return out
}