From 137da5a8934ec82272e40994c4188f1d980354bf Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Thu, 14 Aug 2025 12:01:29 +0300 Subject: [PATCH] Replace env vars with toml config --- .gitignore | 6 +-- .env.example => config.dist.toml | 31 ++++++++--- go.mod | 1 + go.sum | 2 + internal/config/config.go | 90 ++++++++++++++++++++++++++++++++ main.go | 50 ++++++++++++------ 6 files changed, 151 insertions(+), 29 deletions(-) rename .env.example => config.dist.toml (61%) create mode 100644 internal/config/config.go diff --git a/.gitignore b/.gitignore index a534a71..f38658d 100644 --- a/.gitignore +++ b/.gitignore @@ -48,10 +48,8 @@ Thumbs.db # Log files *.log -# Environment files -.env -.env.local -.env.*.local +# Config files +config.toml # Sample and test audio files *.m4a diff --git a/.env.example b/config.dist.toml similarity index 61% rename from .env.example rename to config.dist.toml index 0a4476a..562e6a0 100644 --- a/.env.example +++ b/config.dist.toml @@ -1,25 +1,40 @@ +# Server configuration +[server] +port = 8080 + +# Database configuration +[database] +path = "data/transcriber.db" + # AWS S3 Configuration +[aws] # Регион AWS (например: us-east-1, eu-west-1) -AWS_REGION=us-east-1 +region = "us-east-1" # AWS Access Key ID (получить в AWS Console) -AWS_ACCESS_KEY_ID=your_access_key_id +access_key_id = "your_access_key_id" # AWS Secret Access Key (получить в AWS Console) -AWS_SECRET_ACCESS_KEY=your_secret_access_key +secret_access_key = "your_secret_access_key" # Имя S3 bucket для загрузки файлов -S3_BUCKET_NAME=your_bucket_name +bucket_name = "your_bucket_name" # Кастомный endpoint для S3 (оставить пустым для AWS S3, заполнить для MinIO или других S3-совместимых сервисов) -S3_ENDPOINT= +endpoint = "" # Yandex Cloud Speech-to-Text Configuration +[yandex] # API ключ для доступа к Yandex Cloud (получить в консоли Yandex Cloud) -YANDEX_CLOUD_API_KEY=your_api_key_here +api_key = "your_api_key_here" + # ID папки в Yandex Cloud (получить в консоли Yandex Cloud) -YANDEX_CLOUD_FOLDER_ID=your_folder_id_here +folder_id = "your_folder_id_here" # Telegram Bot Configuration +[telegram] # Токен Telegram бота (получить у @BotFather в Telegram) -TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here +bot_token = "your_telegram_bot_token_here" + +# Таймаут обновлений Telegram бота (в секундах) +update_timeout = 10 diff --git a/go.mod b/go.mod index 695c4d6..c25cb8a 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( ) require ( + github.com/BurntSushi/toml v1.5.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.2 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.2 // indirect diff --git a/go.sum b/go.sum index 7038f84..2339d41 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/aws/aws-sdk-go-v2 v1.37.2 h1:xkW1iMYawzcmYFYEV0UCMxc8gSsjCGEhBXQkdQywVbo= diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..5eab274 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,90 @@ +package config + +import ( + "fmt" + "os" + + "github.com/BurntSushi/toml" +) + +type Config struct { + Server ServerConfig `toml:"server"` + Database DatabaseConfig `toml:"database"` + AWS AWSConfig `toml:"aws"` + Yandex YandexConfig `toml:"yandex"` + Telegram TelegramConfig `toml:"telegram"` +} + +type ServerConfig struct { + Port int `toml:"port"` + ShutdownTimeout int `toml:"shutdown_timeout"` + ForceShutdownTimeout int `toml:"force_shutdown_timeout"` +} + +type DatabaseConfig struct { + Path string `toml:"path"` +} + +type AWSConfig struct { + Region string `toml:"region"` + AccessKey string `toml:"access_key_id"` + SecretKey string `toml:"secret_access_key"` + BucketName string `toml:"bucket_name"` + Endpoint string `toml:"endpoint"` +} + +type YandexConfig struct { + APIKey string `toml:"api_key"` + FolderID string `toml:"folder_id"` +} + +type TelegramConfig struct { + BotToken string `toml:"bot_token"` + UpdateTimeout int `toml:"update_timeout"` +} + +// DefaultConfig returns a Config with default values +func DefaultConfig() *Config { + return &Config{ + Server: ServerConfig{ + Port: 8080, + ShutdownTimeout: 5, + ForceShutdownTimeout: 20, + }, + Database: DatabaseConfig{ + Path: "data/transcriber.db", + }, + AWS: AWSConfig{ + Region: "ru-central1", + AccessKey: "", + SecretKey: "", + BucketName: "", + Endpoint: "", + }, + Yandex: YandexConfig{ + APIKey: "", + FolderID: "", + }, + Telegram: TelegramConfig{ + BotToken: "", + UpdateTimeout: 10, + }, + } +} + +// LoadConfig loads configuration from a TOML file +func LoadConfig(path string) (*Config, error) { + // Check if file exists + if _, err := os.Stat(path); os.IsNotExist(err) { + return nil, fmt.Errorf("config file not found: %s", path) + } + + config := DefaultConfig() + + // Load configuration from file + if _, err := toml.DecodeFile(path, &config); err != nil { + return nil, fmt.Errorf("failed to decode config file: %w", err) + } + + return config, nil +} diff --git a/main.go b/main.go index ed3a7d0..e614f4b 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "context" "database/sql" + "flag" "fmt" "log/slog" "net/http" @@ -16,6 +17,7 @@ import ( ffmpegmv "git.vakhrushev.me/av/transcriber/internal/adapter/metaviewer/ffmpeg" "git.vakhrushev.me/av/transcriber/internal/adapter/recognizer/yandex" "git.vakhrushev.me/av/transcriber/internal/adapter/repo/sqlite" + "git.vakhrushev.me/av/transcriber/internal/config" httpcontroller "git.vakhrushev.me/av/transcriber/internal/controller/http" tgcontroller "git.vakhrushev.me/av/transcriber/internal/controller/tg" "git.vakhrushev.me/av/transcriber/internal/controller/worker" @@ -31,10 +33,8 @@ import ( ) const ( - TelegramUpdateTimeout = 10 ServerShutdownTimeout = 5 - - ForceShutdownTimeout = 20 + ForceShutdownTimeout = 20 ) func main() { @@ -44,6 +44,22 @@ func main() { })) slog.SetDefault(logger) + // Parse command line flags + configPath := flag.String("c", "config.toml", "Path to config file") + flag.StringVar(configPath, "config", "config.toml", "Path to config file (alias for -c)") + flag.Parse() + + // Load configuration + cfg, err := config.LoadConfig(*configPath) + if err != nil { + // If config file doesn't exist, use defaults + cfg = config.DefaultConfig() + // Log that we're using default config + logger.Info("Using default configuration", "config_path", *configPath, "error", err) + } else { + logger.Info("Configuration loaded successfully", "config_path", *configPath) + } + // Загружаем переменные окружения из .env файла if err := godotenv.Load(); err != nil { logger.Warn("Warning: .env file not found, using system environment variables") @@ -55,7 +71,7 @@ func main() { os.Exit(1) } - db, err := sql.Open("sqlite3", "data/transcriber.db") + db, err := sql.Open("sqlite3", cfg.Database.Path) if err != nil { logger.Error("failed to open database", "error", err) os.Exit(1) @@ -84,13 +100,13 @@ func main() { converter := ffmpegconv.NewFfmpegConverter() recognizer, err := yandex.NewYandexAudioRecognizerService(yandex.YandexAudioRecognizerConfig{ - Region: os.Getenv("AWS_REGION"), - AccessKey: os.Getenv("AWS_ACCESS_KEY_ID"), - SecretKey: os.Getenv("AWS_SECRET_ACCESS_KEY"), - BucketName: os.Getenv("S3_BUCKET_NAME"), - Endpoint: os.Getenv("S3_ENDPOINT"), - ApiKey: os.Getenv("YANDEX_CLOUD_API_KEY"), - FolderID: os.Getenv("YANDEX_CLOUD_FOLDER_ID"), + Region: cfg.AWS.Region, + AccessKey: cfg.AWS.AccessKey, + SecretKey: cfg.AWS.SecretKey, + BucketName: cfg.AWS.BucketName, + Endpoint: cfg.AWS.Endpoint, + ApiKey: cfg.Yandex.APIKey, + FolderID: cfg.Yandex.FolderID, }) if err != nil { logger.Error("failed to create audio recognizer", "error", err) @@ -109,8 +125,8 @@ func main() { var wg sync.WaitGroup tgConfig := tgcontroller.TelegramConfig{ - BotToken: os.Getenv("TELEGRAM_BOT_TOKEN"), - UpdateTimeout: TelegramUpdateTimeout, + BotToken: cfg.Telegram.BotToken, + UpdateTimeout: cfg.Telegram.UpdateTimeout, } // Создаем Telegram бот @@ -182,7 +198,7 @@ func main() { // Создаем HTTP сервер srv := &http.Server{ - Addr: ":8080", + Addr: fmt.Sprintf(":%d", cfg.Server.Port), Handler: router, } @@ -190,7 +206,7 @@ func main() { wg.Add(1) go func() { defer wg.Done() - logger.Info("Starting HTTP server", "port", 8080) + logger.Info("Starting HTTP server", "port", cfg.Server.Port) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { logger.Error("HTTP server error", "error", err) } @@ -214,7 +230,7 @@ func main() { } // Создаем контекст с таймаутом для graceful shutdown HTTP сервера - shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), ServerShutdownTimeout*time.Second) + shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), time.Duration(cfg.Server.ShutdownTimeout)*time.Second) defer shutdownCancel() // Останавливаем HTTP сервер @@ -239,7 +255,7 @@ func main() { select { case <-done: logger.Info("All workers stopped gracefully") - case <-time.After(ForceShutdownTimeout * time.Second): + case <-time.After(time.Duration(cfg.Server.ForceShutdownTimeout) * time.Second): logger.Warn("Timeout reached, forcing shutdown") }