Files
nofx/config/config.go
tinkle-community b2ce123df1 fix(auth): single-user deployment by default, no open registration
Registration logic redesigned:
- Empty DB (first-time setup): registration always open, no config needed
- After first user exists: registration closed by default
- Multi-user opt-in: set REGISTRATION_ENABLED=true + MAX_USERS=N in .env

Config defaults changed:
- RegistrationEnabled: true → false (closed after first user)
- MaxUsers: 10 → 1 (single-user deployment default)

This eliminates the confusion of multiple users appearing in a personal
deployment where Telegram is bound to a single admin account.
2026-03-08 18:32:50 +08:00

174 lines
5.0 KiB
Go

package config
import (
"fmt"
"nofx/experience"
"nofx/mcp"
"os"
"strconv"
"strings"
)
// Global configuration instance
var global *Config
// Config is the global configuration (loaded from .env)
// Only contains truly global config, trading related config is at trader/strategy level
type Config struct {
// Service configuration
APIServerPort int
JWTSecret string
RegistrationEnabled bool
MaxUsers int // Maximum number of users allowed (0 = unlimited, default = 10)
// Database configuration
DBType string // sqlite or postgres
DBPath string // SQLite database file path
DBHost string // PostgreSQL host
DBPort int // PostgreSQL port
DBUser string // PostgreSQL user
DBPassword string // PostgreSQL password
DBName string // PostgreSQL database name
DBSSLMode string // PostgreSQL SSL mode
// Security configuration
// TransportEncryption enables browser-side encryption for API keys
// Requires HTTPS or localhost. Set to false for HTTP access via IP.
TransportEncryption bool
// Experience improvement (anonymous usage statistics)
// Helps us understand product usage and improve the experience
// Set EXPERIENCE_IMPROVEMENT=false to disable
ExperienceImprovement bool
// Market data provider API keys
AlpacaAPIKey string // Alpaca API key for US stocks
AlpacaSecretKey string // Alpaca secret key
TwelveDataKey string // TwelveData API key for forex & metals
// Telegram Bot configuration
TelegramBotToken string // TELEGRAM_BOT_TOKEN (required to enable bot)
TelegramAdminChatID int64 // TELEGRAM_ADMIN_CHAT_ID (optional, 0 = auto-bind on first /start)
}
// Init initializes global configuration (from .env)
func Init() {
cfg := &Config{
APIServerPort: 8080,
RegistrationEnabled: false, // Default: closed after first user registers (first-time setup always allowed)
MaxUsers: 1, // Default: single-user deployment
ExperienceImprovement: true, // Default: enabled to help improve the product
// Database defaults
DBType: "sqlite",
DBPath: "data/data.db",
DBHost: "localhost",
DBPort: 5432,
DBUser: "postgres",
DBName: "nofx",
DBSSLMode: "disable",
}
// Load from environment variables
if v := os.Getenv("JWT_SECRET"); v != "" {
cfg.JWTSecret = strings.TrimSpace(v)
}
if cfg.JWTSecret == "" {
cfg.JWTSecret = "default-jwt-secret-change-in-production"
}
if v := os.Getenv("REGISTRATION_ENABLED"); v != "" {
cfg.RegistrationEnabled = strings.ToLower(v) == "true"
}
if v := os.Getenv("MAX_USERS"); v != "" {
if maxUsers, err := strconv.Atoi(v); err == nil && maxUsers >= 0 {
cfg.MaxUsers = maxUsers
}
}
if v := os.Getenv("API_SERVER_PORT"); v != "" {
if port, err := strconv.Atoi(v); err == nil && port > 0 {
cfg.APIServerPort = port
}
}
// Transport encryption: default false for easier deployment
// Set TRANSPORT_ENCRYPTION=true to enable (requires HTTPS or localhost)
if v := os.Getenv("TRANSPORT_ENCRYPTION"); v != "" {
cfg.TransportEncryption = strings.ToLower(v) == "true"
}
// Experience improvement: anonymous usage statistics
// Default enabled, set EXPERIENCE_IMPROVEMENT=false to disable
if v := os.Getenv("EXPERIENCE_IMPROVEMENT"); v != "" {
cfg.ExperienceImprovement = strings.ToLower(v) != "false"
}
// Market data provider API keys
cfg.AlpacaAPIKey = os.Getenv("ALPACA_API_KEY")
cfg.AlpacaSecretKey = os.Getenv("ALPACA_SECRET_KEY")
cfg.TwelveDataKey = os.Getenv("TWELVEDATA_API_KEY")
// Telegram Bot configuration
cfg.TelegramBotToken = os.Getenv("TELEGRAM_BOT_TOKEN")
if chatIDStr := os.Getenv("TELEGRAM_ADMIN_CHAT_ID"); chatIDStr != "" {
if id, err := strconv.ParseInt(chatIDStr, 10, 64); err == nil {
cfg.TelegramAdminChatID = id
} else {
// logger may not be init yet, use fmt
fmt.Printf("WARNING: TELEGRAM_ADMIN_CHAT_ID invalid value %q, ignoring: %v\n", chatIDStr, err)
}
}
// Database configuration
if v := os.Getenv("DB_TYPE"); v != "" {
cfg.DBType = strings.ToLower(v)
}
if v := os.Getenv("DB_PATH"); v != "" {
cfg.DBPath = v
}
if v := os.Getenv("DB_HOST"); v != "" {
cfg.DBHost = v
}
if v := os.Getenv("DB_PORT"); v != "" {
if port, err := strconv.Atoi(v); err == nil && port > 0 {
cfg.DBPort = port
}
}
if v := os.Getenv("DB_USER"); v != "" {
cfg.DBUser = v
}
if v := os.Getenv("DB_PASSWORD"); v != "" {
cfg.DBPassword = v
}
if v := os.Getenv("DB_NAME"); v != "" {
cfg.DBName = v
}
if v := os.Getenv("DB_SSLMODE"); v != "" {
cfg.DBSSLMode = v
}
global = cfg
// Initialize experience improvement (installation ID will be set after database init)
experience.Init(cfg.ExperienceImprovement, "")
// Set up AI token usage tracking callback
mcp.TokenUsageCallback = func(usage mcp.TokenUsage) {
experience.TrackAIUsage(experience.AIUsageEvent{
ModelProvider: usage.Provider,
ModelName: usage.Model,
InputTokens: usage.PromptTokens,
OutputTokens: usage.CompletionTokens,
})
}
}
// Get returns the global configuration
func Get() *Config {
if global == nil {
Init()
}
return global
}