package config import ( "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/sha256" "encoding/base64" "errors" "fmt" "io" "os" "path/filepath" "strings" ) type Config struct { DataDir string DatabasePath string UploadDir string JWTSecret string EncryptionKey []byte } func New(dataDir string) *Config { // 确保data目录存在 os.MkdirAll(dataDir, 0755) // 创建上传目录 uploadDir := filepath.Join(dataDir, "uploads") os.MkdirAll(uploadDir, 0755) // 从环境变量获取JWT密钥,如果没有则使用默认值 jwtSecret := os.Getenv("JWT_SECRET") if jwtSecret == "" { jwtSecret = "your-secret-key-change-in-production" } encryptionSource := os.Getenv("CONFIG_ENCRYPTION_KEY") if encryptionSource == "" { // Keep existing installations decryptable while allowing production deployments // to use a dedicated key that is independent from JWT signing. encryptionSource = jwtSecret } encryptionKey := sha256.Sum256([]byte(encryptionSource)) return &Config{ DataDir: dataDir, DatabasePath: filepath.Join(dataDir, "home.db"), UploadDir: uploadDir, JWTSecret: jwtSecret, EncryptionKey: encryptionKey[:], } } // EncryptSecret encrypts a value for storage in the application database. func (c *Config) EncryptSecret(value string) (string, error) { if strings.TrimSpace(value) == "" { return "", nil } block, err := aes.NewCipher(c.EncryptionKey) if err != nil { return "", err } gcm, err := cipher.NewGCM(block) if err != nil { return "", err } nonce := make([]byte, gcm.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { return "", err } ciphertext := gcm.Seal(nil, nonce, []byte(value), nil) encoded := base64.RawStdEncoding.EncodeToString(append(nonce, ciphertext...)) return "v1:" + encoded, nil } // DecryptSecret decrypts a value previously returned by EncryptSecret. func (c *Config) DecryptSecret(value string) (string, error) { if strings.TrimSpace(value) == "" { return "", nil } if !strings.HasPrefix(value, "v1:") { return "", errors.New("unsupported encrypted secret format") } raw, err := base64.RawStdEncoding.DecodeString(strings.TrimPrefix(value, "v1:")) if err != nil { return "", fmt.Errorf("decode encrypted secret: %w", err) } block, err := aes.NewCipher(c.EncryptionKey) if err != nil { return "", err } gcm, err := cipher.NewGCM(block) if err != nil { return "", err } if len(raw) < gcm.NonceSize() { return "", errors.New("encrypted secret is too short") } plaintext, err := gcm.Open(nil, raw[:gcm.NonceSize()], raw[gcm.NonceSize():], nil) if err != nil { return "", errors.New("encrypted secret authentication failed") } return string(plaintext), nil }