240 lines
7.6 KiB
Go
240 lines
7.6 KiB
Go
package config
|
||
|
||
import (
|
||
"os"
|
||
"strings"
|
||
|
||
"github.com/zeromicro/go-zero/core/stores/cache"
|
||
"github.com/zeromicro/go-zero/rest"
|
||
)
|
||
|
||
// Config — go-zero 風格:RestConf + CacheRedis + Mongo URI.
|
||
type Config struct {
|
||
rest.RestConf
|
||
|
||
// CacheRedis is go-zero cache.CacheConf (feeds monc Redis 快取).
|
||
CacheRedis cache.CacheConf
|
||
|
||
Mongo struct {
|
||
URI string
|
||
Database string
|
||
}
|
||
|
||
Auth struct {
|
||
AccessSecret string
|
||
AccessExpire int64
|
||
RefreshSecret string
|
||
RefreshExpire int64
|
||
}
|
||
Platform struct {
|
||
// AIKey — legacy alias for XAIKey
|
||
AIKey string `json:",optional"`
|
||
// XAIKey — platform BYOK for xAI (api.x.ai)
|
||
XAIKey string `json:",optional"`
|
||
// OpenCodeKey — platform BYOK for OpenCode Go (opencode.ai/zen/go)
|
||
OpenCodeKey string `json:",optional"`
|
||
ExaKey string `json:",optional"`
|
||
ThreadsAppId string `json:",optional"`
|
||
ThreadsAppSecret string `json:",optional"`
|
||
}
|
||
Worker struct {
|
||
ID string `json:",default=worker-1"`
|
||
PollIntervalMs int `json:",default=2000"`
|
||
}
|
||
// Bcrypt cost (stand-alone Bcrypt.Cost)
|
||
Bcrypt struct {
|
||
Cost int `json:",default=10"`
|
||
}
|
||
// OAuth third-party (optional; empty → mock path in callback)
|
||
OAuth struct {
|
||
GoogleClientID string `json:",optional"`
|
||
LineClientID string `json:",optional"`
|
||
LineClientSecret string `json:",optional"`
|
||
LineRedirectURI string `json:",optional"`
|
||
}
|
||
// Mail — 驗證碼/重設密碼寄信(SMTP;空 Host 則只打 log + 可回 mock_code)
|
||
Mail struct {
|
||
Sender string `json:",optional"` // e.g. "Harbor Desk <noreply@example.com>"
|
||
SMTP struct {
|
||
Host string `json:",optional"`
|
||
Port int `json:",default=587"`
|
||
User string `json:",optional"`
|
||
Password string `json:",optional"`
|
||
} `json:",optional"`
|
||
// DevExposeCode: API 回 mock_code(本機沒信箱時用)。正式請 false。
|
||
DevExposeCode bool `json:",default=true"`
|
||
} `json:",optional"`
|
||
// PublicWebBase — 前端 origin,用於重設密碼信內連結/logo(勿尾斜線)
|
||
// 例:http://127.0.0.1:5173 或 https://threads-tool-dev.30cm.net
|
||
PublicWebBase string `json:",optional,default=http://127.0.0.1:5173"`
|
||
// PublicAPIBase — 對外 API origin(Threads OAuth redirect_uri 必須 https 公開網址)
|
||
// 空則:若 PublicWebBase 為 https 則同 host;否則回落 http://127.0.0.1:Port
|
||
// 例:https://threads-tool-dev.30cm.net
|
||
PublicAPIBase string `json:",optional"`
|
||
// Brand — 郵件 chrome(hermes);LogoURL 空則用 PublicWebBase + /brand-mark.jpg
|
||
Brand struct {
|
||
Name string `json:",optional,default=Harbor Desk"`
|
||
LogoURL string `json:",optional"` // absolute URL
|
||
Copyright string `json:",optional,default=© Harbor Desk"`
|
||
} `json:",optional"`
|
||
// ObjectStorage — 頭像等二進位;**僅 MinIO / S3**(不落本機磁碟)
|
||
ObjectStorage struct {
|
||
Endpoint string `json:",optional"` // 必填,e.g. http://127.0.0.1:9000
|
||
Region string `json:",optional,default=us-east-1"`
|
||
Bucket string `json:",optional,default=haixun-assets"`
|
||
AccessKey string `json:",optional"`
|
||
SecretKey string `json:",optional"`
|
||
UsePathStyle bool `json:",optional,default=true"`
|
||
PublicBaseURL string `json:",optional"` // 公開讀取前綴,勿尾斜線
|
||
} `json:",optional"`
|
||
}
|
||
|
||
// ApplyEnv overlays secrets from environment.
|
||
func (c *Config) ApplyEnv() {
|
||
if v := os.Getenv("MONGO_URI"); v != "" {
|
||
c.Mongo.URI = v
|
||
}
|
||
if v := os.Getenv("MONGO_DATABASE"); v != "" {
|
||
c.Mongo.Database = v
|
||
}
|
||
if v := os.Getenv("REDIS_HOST"); v != "" && len(c.CacheRedis) > 0 {
|
||
c.CacheRedis[0].RedisConf.Host = v
|
||
}
|
||
if len(c.CacheRedis) > 0 {
|
||
// monc 快取 Redis 帳密(本機 infra 常開 requirepass)
|
||
if v := os.Getenv("REDIS_PASSWORD"); v != "" {
|
||
c.CacheRedis[0].RedisConf.Pass = v
|
||
} else if v := os.Getenv("HAIXUN_REDIS_PASSWORD"); v != "" {
|
||
c.CacheRedis[0].RedisConf.Pass = v
|
||
}
|
||
if v := os.Getenv("HAIXUN_REDIS_ADDR"); v != "" {
|
||
c.CacheRedis[0].RedisConf.Host = v
|
||
}
|
||
}
|
||
if v := os.Getenv("AUTH_ACCESS_SECRET"); v != "" {
|
||
c.Auth.AccessSecret = v
|
||
}
|
||
if v := os.Getenv("AUTH_REFRESH_SECRET"); v != "" {
|
||
c.Auth.RefreshSecret = v
|
||
}
|
||
if v := os.Getenv("PLATFORM_AI_KEY"); v != "" {
|
||
c.Platform.AIKey = v
|
||
}
|
||
if v := firstEnv("PLATFORM_XAI_KEY", "XAI_API_KEY"); v != "" {
|
||
c.Platform.XAIKey = v
|
||
}
|
||
if v := firstEnv("PLATFORM_OPENCODE_KEY", "OPENCODE_API_KEY", "OPENCODE_GO_API_KEY"); v != "" {
|
||
c.Platform.OpenCodeKey = v
|
||
}
|
||
// legacy AIKey → XAI when XAI empty
|
||
if c.Platform.XAIKey == "" && c.Platform.AIKey != "" {
|
||
c.Platform.XAIKey = c.Platform.AIKey
|
||
}
|
||
if v := os.Getenv("PLATFORM_EXA_KEY"); v != "" {
|
||
c.Platform.ExaKey = v
|
||
}
|
||
if v := firstEnv("THREADS_APP_ID", "HAIXUN_THREADS_APP_ID"); v != "" {
|
||
c.Platform.ThreadsAppId = v
|
||
}
|
||
if v := firstEnv("THREADS_APP_SECRET", "HAIXUN_THREADS_APP_SECRET"); v != "" {
|
||
c.Platform.ThreadsAppSecret = v
|
||
}
|
||
if v := os.Getenv("WORKER_ID"); v != "" {
|
||
c.Worker.ID = v
|
||
}
|
||
if v := os.Getenv("PORT"); v != "" {
|
||
var p int
|
||
for _, ch := range v {
|
||
if ch < '0' || ch > '9' {
|
||
return
|
||
}
|
||
p = p*10 + int(ch-'0')
|
||
}
|
||
if p > 0 {
|
||
c.Port = p
|
||
}
|
||
}
|
||
// Mail / SMTP(MAIL_* 優先;HAIXUN_SMTP_* 與 old/infra 相容)
|
||
if v := firstEnv("MAIL_SENDER", "HAIXUN_SMTP_FROM"); v != "" {
|
||
c.Mail.Sender = v
|
||
}
|
||
if v := firstEnv("MAIL_SMTP_HOST", "HAIXUN_SMTP_HOST"); v != "" {
|
||
c.Mail.SMTP.Host = v
|
||
}
|
||
if v := firstEnv("MAIL_SMTP_USER", "HAIXUN_SMTP_USERNAME"); v != "" {
|
||
c.Mail.SMTP.User = v
|
||
}
|
||
if v := firstEnv("MAIL_SMTP_PASSWORD", "HAIXUN_SMTP_PASSWORD"); v != "" {
|
||
c.Mail.SMTP.Password = v
|
||
}
|
||
if v := firstEnv("MAIL_SMTP_PORT", "HAIXUN_SMTP_PORT"); v != "" {
|
||
if p := parsePort(v); p > 0 {
|
||
c.Mail.SMTP.Port = p
|
||
}
|
||
}
|
||
if v := os.Getenv("MAIL_DEV_EXPOSE_CODE"); v == "0" || v == "false" || v == "FALSE" {
|
||
c.Mail.DevExposeCode = false
|
||
} else if v == "1" || v == "true" || v == "TRUE" {
|
||
c.Mail.DevExposeCode = true
|
||
}
|
||
if v := firstEnv("PUBLIC_WEB_BASE", "HAIXUN_PUBLIC_WEB_BASE"); v != "" {
|
||
c.PublicWebBase = strings.TrimRight(v, "/")
|
||
}
|
||
if v := firstEnv("PUBLIC_API_BASE", "HAIXUN_PUBLIC_API_BASE"); v != "" {
|
||
c.PublicAPIBase = strings.TrimRight(v, "/")
|
||
}
|
||
if v := os.Getenv("MAIL_BRAND_NAME"); v != "" {
|
||
c.Brand.Name = v
|
||
}
|
||
if v := firstEnv("MAIL_LOGO_URL", "HAIXUN_MAIL_LOGO_URL"); v != "" {
|
||
c.Brand.LogoURL = v
|
||
}
|
||
if v := os.Getenv("MAIL_COPYRIGHT"); v != "" {
|
||
c.Brand.Copyright = v
|
||
}
|
||
// Object storage (HAIXUN_STORAGE_S3_* 與 old/infra 相容)
|
||
if v := firstEnv("OBJECT_STORAGE_ENDPOINT", "HAIXUN_STORAGE_S3_ENDPOINT"); v != "" {
|
||
c.ObjectStorage.Endpoint = v
|
||
}
|
||
if v := firstEnv("OBJECT_STORAGE_REGION", "HAIXUN_STORAGE_S3_REGION"); v != "" {
|
||
c.ObjectStorage.Region = v
|
||
}
|
||
if v := firstEnv("OBJECT_STORAGE_BUCKET", "HAIXUN_STORAGE_S3_BUCKET"); v != "" {
|
||
c.ObjectStorage.Bucket = v
|
||
}
|
||
if v := firstEnv("OBJECT_STORAGE_ACCESS_KEY", "HAIXUN_STORAGE_S3_ACCESS_KEY", "MINIO_ROOT_USER"); v != "" {
|
||
c.ObjectStorage.AccessKey = v
|
||
}
|
||
if v := firstEnv("OBJECT_STORAGE_SECRET_KEY", "HAIXUN_STORAGE_S3_SECRET_KEY", "MINIO_ROOT_PASSWORD"); v != "" {
|
||
c.ObjectStorage.SecretKey = v
|
||
}
|
||
if v := firstEnv("OBJECT_STORAGE_PUBLIC_BASE_URL", "HAIXUN_STORAGE_S3_PUBLIC_BASE_URL"); v != "" {
|
||
c.ObjectStorage.PublicBaseURL = strings.TrimRight(v, "/")
|
||
}
|
||
if v := os.Getenv("HAIXUN_STORAGE_S3_USE_PATH_STYLE"); v == "0" || v == "false" {
|
||
c.ObjectStorage.UsePathStyle = false
|
||
} else if v == "1" || v == "true" {
|
||
c.ObjectStorage.UsePathStyle = true
|
||
}
|
||
}
|
||
|
||
func firstEnv(keys ...string) string {
|
||
for _, k := range keys {
|
||
if v := os.Getenv(k); v != "" {
|
||
return v
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func parsePort(v string) int {
|
||
var p int
|
||
for _, ch := range v {
|
||
if ch < '0' || ch > '9' {
|
||
return 0
|
||
}
|
||
p = p*10 + int(ch-'0')
|
||
}
|
||
return p
|
||
}
|