Files
ncore-hnr/internal/config/config.go
Zsolt Alföldi 469e5b0678 init
2026-05-07 00:14:02 +02:00

210 lines
7.1 KiB
Go

package config
import (
"flag"
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/joho/godotenv"
)
type Config struct {
NCoreUsername string
NCorePassword string
NCoreLoginURL string
NCoreHitRunURL string
QBitURL string
QBitUsername string
QBitPassword string
SkipQBit bool
DBPath string
DryRun bool
JSONOutput bool
AlertAfter time.Duration
HTTPTimeout time.Duration
NotificationType string
NotificationNTFYURL string
NotificationSMTPHost string
NotificationSMTPPort string
NotificationSMTPUsername string
NotificationSMTPPassword string
NotificationSMTPFrom string
NotificationSMTPTo string
}
func Load(args []string) (Config, error) {
loadDotenv(".env", "../.env")
cfg := Config{
NCoreUsername: env("NCORE_USERNAME", ""),
NCorePassword: env("NCORE_PASSWORD", ""),
NCoreLoginURL: env("NCORE_LOGIN_URL", "https://ncore.pro/login.php"),
NCoreHitRunURL: env("NCORE_HITRUN_URL", "https://ncore.pro/hitnrun.php"),
QBitURL: env("QBITTORRENT_URL", ""),
QBitUsername: env("QBITTORRENT_USERNAME", ""),
QBitPassword: env("QBITTORRENT_PASSWORD", ""),
SkipQBit: envBool("SKIP_QBIT", false),
DBPath: env("APP_DB_PATH", "data/ncore-hnr.sqlite"),
DryRun: envBool("DRY_RUN", false),
JSONOutput: envBool("JSON_OUTPUT", false),
AlertAfter: envDuration("ALERT_AFTER", 48*time.Hour),
HTTPTimeout: envDuration("HTTP_TIMEOUT", 30*time.Second),
NotificationType: env("NOTIFICATION_TYPE", ""),
NotificationNTFYURL: env("NOTIFICATION_NTFY_URL", env("NOTIFY_URL", "")),
NotificationSMTPHost: env("NOTIFICATION_SMTP_HOST", ""),
NotificationSMTPPort: env("NOTIFICATION_SMTP_PORT", "587"),
NotificationSMTPUsername: env("NOTIFICATION_SMTP_USERNAME", ""),
NotificationSMTPPassword: env("NOTIFICATION_SMTP_PASSWORD", ""),
NotificationSMTPFrom: env("NOTIFICATION_SMTP_FROM", ""),
NotificationSMTPTo: env("NOTIFICATION_SMTP_TO", ""),
}
fs := flag.NewFlagSet("ncore-hnr", flag.ContinueOnError)
fs.StringVar(&cfg.DBPath, "db", cfg.DBPath, "SQLite database path")
fs.BoolVar(&cfg.DryRun, "dry-run", cfg.DryRun, "print intended qBittorrent actions without executing them")
fs.BoolVar(&cfg.SkipQBit, "skip-qbit", cfg.SkipQBit, "skip qBittorrent matching/actions")
fs.BoolVar(&cfg.JSONOutput, "json", cfg.JSONOutput, "print JSON summary")
fs.DurationVar(&cfg.AlertAfter, "alert-after", cfg.AlertAfter, "mark active torrents manual-needed after this duration")
fs.StringVar(&cfg.NotificationType, "notification-type", cfg.NotificationType, "notification sender: ntfy, smtp, or empty")
fs.StringVar(&cfg.NotificationNTFYURL, "notification-ntfy-url", cfg.NotificationNTFYURL, "ntfy topic URL for manual-needed alerts")
fs.StringVar(&cfg.NotificationNTFYURL, "notify-url", cfg.NotificationNTFYURL, "deprecated: use --notification-ntfy-url")
fs.StringVar(&cfg.NotificationSMTPHost, "notification-smtp-host", cfg.NotificationSMTPHost, "SMTP host for manual-needed alerts")
fs.StringVar(&cfg.NotificationSMTPPort, "notification-smtp-port", cfg.NotificationSMTPPort, "SMTP port for manual-needed alerts")
fs.StringVar(&cfg.NotificationSMTPUsername, "notification-smtp-username", cfg.NotificationSMTPUsername, "SMTP username for manual-needed alerts")
fs.StringVar(&cfg.NotificationSMTPPassword, "notification-smtp-password", cfg.NotificationSMTPPassword, "SMTP password for manual-needed alerts")
fs.StringVar(&cfg.NotificationSMTPFrom, "notification-smtp-from", cfg.NotificationSMTPFrom, "SMTP From header for manual-needed alerts")
fs.StringVar(&cfg.NotificationSMTPTo, "notification-smtp-to", cfg.NotificationSMTPTo, "SMTP To header for manual-needed alerts")
fs.DurationVar(&cfg.HTTPTimeout, "http-timeout", cfg.HTTPTimeout, "HTTP timeout")
if err := fs.Parse(args); err != nil {
return cfg, err
}
cfg.NotificationType = normalizeNotificationType(cfg)
if cfg.NCoreUsername == "" {
return cfg, fmt.Errorf("missing NCORE_USERNAME")
}
if cfg.NCorePassword == "" {
return cfg, fmt.Errorf("missing NCORE_PASSWORD")
}
if !cfg.SkipQBit && !cfg.DryRun {
if cfg.QBitURL == "" || cfg.QBitUsername == "" || cfg.QBitPassword == "" {
return cfg, fmt.Errorf("set QBITTORRENT_URL, QBITTORRENT_USERNAME and QBITTORRENT_PASSWORD, or use --dry-run/--skip-qbit")
}
}
if err := validateNotification(cfg); err != nil {
return cfg, err
}
return cfg, nil
}
func LoadReadOnly(args []string) (Config, error) {
loadDotenv(".env", "../.env")
cfg := Config{
DBPath: env("APP_DB_PATH", "data/ncore-hnr.sqlite"),
JSONOutput: envBool("JSON_OUTPUT", false),
}
fs := flag.NewFlagSet("ncore-hnr", flag.ContinueOnError)
fs.StringVar(&cfg.DBPath, "db", cfg.DBPath, "SQLite database path")
fs.BoolVar(&cfg.JSONOutput, "json", cfg.JSONOutput, "print JSON output")
if err := fs.Parse(args); err != nil {
return cfg, err
}
return cfg, nil
}
func loadDotenv(paths ...string) {
for _, path := range paths {
_ = godotenv.Load(path)
}
}
func env(name string, fallback string) string {
value := strings.TrimSpace(os.Getenv(name))
if value == "" {
return fallback
}
return value
}
func envBool(name string, fallback bool) bool {
value := strings.TrimSpace(os.Getenv(name))
if value == "" {
return fallback
}
parsed, err := strconv.ParseBool(value)
if err != nil {
return fallback
}
return parsed
}
func envDuration(name string, fallback time.Duration) time.Duration {
value := strings.TrimSpace(os.Getenv(name))
if value == "" {
return fallback
}
parsed, err := time.ParseDuration(value)
if err != nil {
return fallback
}
return parsed
}
func normalizeNotificationType(cfg Config) string {
notificationType := strings.ToLower(strings.TrimSpace(cfg.NotificationType))
switch notificationType {
case "none", "off", "disabled":
return ""
case "":
if strings.TrimSpace(cfg.NotificationNTFYURL) != "" {
return "ntfy"
}
if strings.TrimSpace(cfg.NotificationSMTPHost) != "" || strings.TrimSpace(cfg.NotificationSMTPTo) != "" {
return "smtp"
}
}
return notificationType
}
func validateNotification(cfg Config) error {
switch cfg.NotificationType {
case "":
return nil
case "ntfy":
if strings.TrimSpace(cfg.NotificationNTFYURL) == "" {
return fmt.Errorf("missing NOTIFICATION_NTFY_URL for ntfy notifications")
}
case "smtp":
if strings.TrimSpace(cfg.NotificationSMTPHost) == "" {
return fmt.Errorf("missing NOTIFICATION_SMTP_HOST for smtp notifications")
}
if strings.TrimSpace(cfg.NotificationSMTPPort) == "" {
return fmt.Errorf("missing NOTIFICATION_SMTP_PORT for smtp notifications")
}
if strings.TrimSpace(cfg.NotificationSMTPFrom) == "" {
return fmt.Errorf("missing NOTIFICATION_SMTP_FROM for smtp notifications")
}
if strings.TrimSpace(cfg.NotificationSMTPTo) == "" {
return fmt.Errorf("missing NOTIFICATION_SMTP_TO for smtp notifications")
}
if (strings.TrimSpace(cfg.NotificationSMTPUsername) == "") != (strings.TrimSpace(cfg.NotificationSMTPPassword) == "") {
return fmt.Errorf("set both NOTIFICATION_SMTP_USERNAME and NOTIFICATION_SMTP_PASSWORD, or leave both empty")
}
default:
return fmt.Errorf("unsupported NOTIFICATION_TYPE %q", cfg.NotificationType)
}
return nil
}