Replace env vars with toml config

This commit is contained in:
2025-08-14 12:01:29 +03:00
parent 121585f807
commit 137da5a893
6 changed files with 151 additions and 29 deletions

6
.gitignore vendored
View File

@@ -48,10 +48,8 @@ Thumbs.db
# Log files # Log files
*.log *.log
# Environment files # Config files
.env config.toml
.env.local
.env.*.local
# Sample and test audio files # Sample and test audio files
*.m4a *.m4a

View File

@@ -1,25 +1,40 @@
# Server configuration
[server]
port = 8080
# Database configuration
[database]
path = "data/transcriber.db"
# AWS S3 Configuration # AWS S3 Configuration
[aws]
# Регион AWS (например: us-east-1, eu-west-1) # Регион 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 (получить в 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 (получить в AWS Console)
AWS_SECRET_ACCESS_KEY=your_secret_access_key secret_access_key = "your_secret_access_key"
# Имя S3 bucket для загрузки файлов # Имя S3 bucket для загрузки файлов
S3_BUCKET_NAME=your_bucket_name bucket_name = "your_bucket_name"
# Кастомный endpoint для S3 (оставить пустым для AWS S3, заполнить для MinIO или других S3-совместимых сервисов) # Кастомный endpoint для S3 (оставить пустым для AWS S3, заполнить для MinIO или других S3-совместимых сервисов)
S3_ENDPOINT= endpoint = ""
# Yandex Cloud Speech-to-Text Configuration # Yandex Cloud Speech-to-Text Configuration
[yandex]
# API ключ для доступа к Yandex Cloud (получить в консоли Yandex Cloud) # API ключ для доступа к Yandex Cloud (получить в консоли Yandex Cloud)
YANDEX_CLOUD_API_KEY=your_api_key_here api_key = "your_api_key_here"
# ID папки в Yandex Cloud (получить в консоли Yandex Cloud) # ID папки в Yandex Cloud (получить в консоли Yandex Cloud)
YANDEX_CLOUD_FOLDER_ID=your_folder_id_here folder_id = "your_folder_id_here"
# Telegram Bot Configuration # Telegram Bot Configuration
[telegram]
# Токен Telegram бота (получить у @BotFather в Telegram) # Токен Telegram бота (получить у @BotFather в Telegram)
TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here bot_token = "your_telegram_bot_token_here"
# Таймаут обновлений Telegram бота (в секундах)
update_timeout = 10

1
go.mod
View File

@@ -23,6 +23,7 @@ require (
) )
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/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/feature/ec2/imds v1.18.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.2 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.2 // indirect

2
go.sum
View File

@@ -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 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= 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= github.com/aws/aws-sdk-go-v2 v1.37.2 h1:xkW1iMYawzcmYFYEV0UCMxc8gSsjCGEhBXQkdQywVbo=

90
internal/config/config.go Normal file
View File

@@ -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
}

50
main.go
View File

@@ -3,6 +3,7 @@ package main
import ( import (
"context" "context"
"database/sql" "database/sql"
"flag"
"fmt" "fmt"
"log/slog" "log/slog"
"net/http" "net/http"
@@ -16,6 +17,7 @@ import (
ffmpegmv "git.vakhrushev.me/av/transcriber/internal/adapter/metaviewer/ffmpeg" 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/recognizer/yandex"
"git.vakhrushev.me/av/transcriber/internal/adapter/repo/sqlite" "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" httpcontroller "git.vakhrushev.me/av/transcriber/internal/controller/http"
tgcontroller "git.vakhrushev.me/av/transcriber/internal/controller/tg" tgcontroller "git.vakhrushev.me/av/transcriber/internal/controller/tg"
"git.vakhrushev.me/av/transcriber/internal/controller/worker" "git.vakhrushev.me/av/transcriber/internal/controller/worker"
@@ -31,10 +33,8 @@ import (
) )
const ( const (
TelegramUpdateTimeout = 10
ServerShutdownTimeout = 5 ServerShutdownTimeout = 5
ForceShutdownTimeout = 20
ForceShutdownTimeout = 20
) )
func main() { func main() {
@@ -44,6 +44,22 @@ func main() {
})) }))
slog.SetDefault(logger) 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 файла // Загружаем переменные окружения из .env файла
if err := godotenv.Load(); err != nil { if err := godotenv.Load(); err != nil {
logger.Warn("Warning: .env file not found, using system environment variables") logger.Warn("Warning: .env file not found, using system environment variables")
@@ -55,7 +71,7 @@ func main() {
os.Exit(1) os.Exit(1)
} }
db, err := sql.Open("sqlite3", "data/transcriber.db") db, err := sql.Open("sqlite3", cfg.Database.Path)
if err != nil { if err != nil {
logger.Error("failed to open database", "error", err) logger.Error("failed to open database", "error", err)
os.Exit(1) os.Exit(1)
@@ -84,13 +100,13 @@ func main() {
converter := ffmpegconv.NewFfmpegConverter() converter := ffmpegconv.NewFfmpegConverter()
recognizer, err := yandex.NewYandexAudioRecognizerService(yandex.YandexAudioRecognizerConfig{ recognizer, err := yandex.NewYandexAudioRecognizerService(yandex.YandexAudioRecognizerConfig{
Region: os.Getenv("AWS_REGION"), Region: cfg.AWS.Region,
AccessKey: os.Getenv("AWS_ACCESS_KEY_ID"), AccessKey: cfg.AWS.AccessKey,
SecretKey: os.Getenv("AWS_SECRET_ACCESS_KEY"), SecretKey: cfg.AWS.SecretKey,
BucketName: os.Getenv("S3_BUCKET_NAME"), BucketName: cfg.AWS.BucketName,
Endpoint: os.Getenv("S3_ENDPOINT"), Endpoint: cfg.AWS.Endpoint,
ApiKey: os.Getenv("YANDEX_CLOUD_API_KEY"), ApiKey: cfg.Yandex.APIKey,
FolderID: os.Getenv("YANDEX_CLOUD_FOLDER_ID"), FolderID: cfg.Yandex.FolderID,
}) })
if err != nil { if err != nil {
logger.Error("failed to create audio recognizer", "error", err) logger.Error("failed to create audio recognizer", "error", err)
@@ -109,8 +125,8 @@ func main() {
var wg sync.WaitGroup var wg sync.WaitGroup
tgConfig := tgcontroller.TelegramConfig{ tgConfig := tgcontroller.TelegramConfig{
BotToken: os.Getenv("TELEGRAM_BOT_TOKEN"), BotToken: cfg.Telegram.BotToken,
UpdateTimeout: TelegramUpdateTimeout, UpdateTimeout: cfg.Telegram.UpdateTimeout,
} }
// Создаем Telegram бот // Создаем Telegram бот
@@ -182,7 +198,7 @@ func main() {
// Создаем HTTP сервер // Создаем HTTP сервер
srv := &http.Server{ srv := &http.Server{
Addr: ":8080", Addr: fmt.Sprintf(":%d", cfg.Server.Port),
Handler: router, Handler: router,
} }
@@ -190,7 +206,7 @@ func main() {
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() 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 { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logger.Error("HTTP server error", "error", err) logger.Error("HTTP server error", "error", err)
} }
@@ -214,7 +230,7 @@ func main() {
} }
// Создаем контекст с таймаутом для graceful shutdown HTTP сервера // Создаем контекст с таймаутом для 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() defer shutdownCancel()
// Останавливаем HTTP сервер // Останавливаем HTTP сервер
@@ -239,7 +255,7 @@ func main() {
select { select {
case <-done: case <-done:
logger.Info("All workers stopped gracefully") 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") logger.Warn("Timeout reached, forcing shutdown")
} }