210 lines
7.1 KiB
Go
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
|
|
}
|