package config import ( "os" "path/filepath" "strings" "time" "git.misaka.ren/M1saka/zhanlu_proxy/internal/auth" ) type Config struct { ListenAddr string ServerBaseURL string UpstreamPath string CredentialsPath string SSOExchangeURL string SSOBaseURL string TokenDecryptKey string PublicKeyPEM string PhonePublicKeyPEM string Models []string DefaultModel string OpenAIAPIKey string LoginPassword string UpstreamTimeout time.Duration StreamIdleTimout time.Duration Debug bool Credentials auth.Credentials } func Load() (Config, error) { cfg := Config{ ListenAddr: getenv("ZHANLU_LISTEN_ADDR", ":8080"), ServerBaseURL: getenv("ZHANLU_SERVER_BASE_URL", "https://api-wuxi-1.cmecloud.cn:8443"), UpstreamPath: getenv("ZHANLU_UPSTREAM_PATH", "/api/acepilot/zhanlu/aiDeveloper/chat"), CredentialsPath: getenv("ZHANLU_CREDENTIALS_FILE", defaultCredentialsPath()), SSOBaseURL: getenv("ZHANLU_SSO_BASE_URL", "http://rdcloud.4c.hq.cmcc"), SSOExchangeURL: getenv("ZHANLU_SSO_EXCHANGE_URL", "https://api-wuxi-1.cmecloud.cn:8443/cmdevops-aiplus-agent-gateway/api/acepilot/zhanlu/checkoutCode"), TokenDecryptKey: os.Getenv("ZHANLU_TOKEN_DECRYPT_KEY"), PublicKeyPEM: getenv("ZHANLU_PUBLIC_KEY_PEM", defaultPublicKeyPEM), PhonePublicKeyPEM: getenv("ZHANLU_PHONE_PUBLIC_KEY_PEM", defaultPhonePublicKeyPEM), DefaultModel: getenv("ZHANLU_DEFAULT_MODEL", "minimax-m25"), OpenAIAPIKey: os.Getenv("OPENAI_COMPAT_API_KEY"), LoginPassword: os.Getenv("ZHANLU_LOGIN_PASSWORD"), UpstreamTimeout: durationEnv("ZHANLU_UPSTREAM_TIMEOUT", 300*time.Second), StreamIdleTimout: durationEnv("ZHANLU_STREAM_IDLE_TIMEOUT", 300*time.Second), Debug: strings.EqualFold(os.Getenv("ZHANLU_DEBUG"), "true"), } cfg.Models = splitCSV(getenv("ZHANLU_MODELS", "glm47,minimax-m25")) cfg.Credentials = auth.Credentials{ AccessKey: os.Getenv("ZHANLU_ACCESS_KEY"), SecretKey: os.Getenv("ZHANLU_SECRET_KEY"), Token: os.Getenv("ZHANLU_TOKEN"), } if cfg.Credentials.Validate() == nil { return cfg, nil } if creds, err := auth.LoadCredentials(cfg.CredentialsPath); err == nil { cfg.Credentials = creds } return cfg, nil } func getenv(key, fallback string) string { if v := strings.TrimSpace(os.Getenv(key)); v != "" { return v } return fallback } func splitCSV(s string) []string { parts := strings.Split(s, ",") out := make([]string, 0, len(parts)) for _, p := range parts { p = strings.TrimSpace(p) if p != "" { out = append(out, p) } } return out } func durationEnv(key string, fallback time.Duration) time.Duration { v := strings.TrimSpace(os.Getenv(key)) if v == "" { return fallback } d, err := time.ParseDuration(v) if err != nil { return fallback } return d } func defaultCredentialsPath() string { return filepath.Join(".", "credentials.json") } const defaultPublicKeyPEM = `-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhxudxTewPgljUHEZHkusP7m3I+zA4/RGvuUMt6TtII/m4zwUOm/Y31zHBTmkCCt8k5vj9y+AmO0TsGmHooNQuMebakdmEWdcA5h7YAHHFbF2w5LcxIXjib08vgVpA+m3R5xPbLK+vfHe2aAX36b5nHReDNncY5vAl3U4CgIEBGPqyG67vJytRWqP+sfEdw5+m192Rf4SCGyiBzRmjiVlH3zeEBjdbOrkAnzKOVz6AHBl2q7LPLJKIzxjoAyhEp5qnDjHUFo5VZUgFwUOt83A/jbGMyzmjRoxBuvKcs9tBuorZyUwIsZN6E+rtQk2YqMPj4RkDsZ7LRmj6on8sN2rHQIDAQAB -----END PUBLIC KEY-----` const defaultPhonePublicKeyPEM = `-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnqiA2qP9BNvKw5DnVnrBVBhd+5gJDVn3mDemCfq/AN1cdaHV57hQo6R1ufp45mOkSwLaJcTE82zFKmgKoEAKwD1SR10rp0xJC7x3yvx2FbpEsiW9TeZlvJdri1BYKUMS8OP8ykjHSJoy0oMaV6e95R2rsu4DEH7JuA9+Bt0sOoLewvHx/fs1e28tH+928uUEKdLug+cv/XTKjLudpLjiSMPZU6EHFqrUhA9zmEasOMmg9Dj0j4sChBooCeCGnh/pYHJaosH5amhlSQ8FnEG0BQBrQbZ+qhRH4LYyqGYN8grDNeSnPj7vPDcwiEm++85i5AngZfEMnGWZg5jYDhO9+QIDAQAB -----END PUBLIC KEY-----`