Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(config): add environment-only mode #167

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Afterwards you need to make the binary executable by running the following comma
chmod +x /usr/bin/seasonpackarr
```

Note: If the commands fail, prefix them with `sudo ` and run them again.
Note: If the commands fail, prefix them with `sudo` and run them again.

#### Systemd (Recommended)

Expand Down Expand Up @@ -142,7 +142,7 @@ of this clearer:

- Announce name: `Show.S01.1080p.WEB-DL.DDPA5.1.H.264-RlsGrp`
- Folder name: `Show.S01.1080p.WEB-DL.DDP5.1.H.264-RlsGrp`

Using the announce name would create the wrong folder and would lead to all the files in the torrent being downloaded
again. The issue in the given example is the additional `A` after `DDP` which is not present in the folder name. By
using the parsed folder name the files will be hardlinked into the exact folder that is being used in the torrent.
Expand Down Expand Up @@ -252,11 +252,14 @@ along in the `HTTP Request Headers` of your autobrr request; if not, the request
headers to pass the API token, but I'll explain both options here.

1. **Header**: Edit the `HTTP Request Headers` field and replace `api_token` with the token you set in your config.

```
X-API-Token=api_token
```

2. **Query Parameter**: Append `?apikey=api_token` at the end of your `Endpoint` field and replace `api_token` with the
token you've set in your config.

```
http://host:port/api/pack?apikey=api_token
```
Expand Down Expand Up @@ -305,7 +308,7 @@ Navigate to the `Actions` tab, click on `Add new` and change the `Action type` o
Depending on whether you intend to only send to qBittorrent or also integrate with Sonarr, you'll need to fill out different fields.

1. **Only qBittorrent**: Fill in the `Save Path` field with the directory where your torrent data resides, for instance
`/data/torrents`, or the `Category` field with a qBittorrent category that saves to your desired location.
`/data/torrents`, or the `Category` field with a qBittorrent category that saves to your desired location.
2. **Sonarr Integration**: Fill in the `Category` field with the category that Sonarr utilizes for all its downloads,
such as `tv-hd` or `tv-uhd`.

Expand Down
39 changes: 30 additions & 9 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,37 @@ services:
ports:
- "127.0.0.1:42069:42069"
environment:
- SEASONPACKARR__HOST=
- SEASONPACKARR__PORT=
- SEASONPACKARR__LOG_LEVEL=
- SEASONPACKARR__LOG_PATH=
- SEASONPACKARR__LOG_MAX_SIZE=
- SEASONPACKARR__LOG_MAX_BACKUPS=
- SEASONPACKARR__SMART_MODE=
- SEASONPACKARR__SMART_MODE_THRESHOLD=
- SEASONPACKARR__PARSE_TORRENT_FILE=
# Core settings
- SEASONPACKARR__DISABLE_CONFIG_FILE=false # set to true to not rely on config.yaml
- SEASONPACKARR__HOST=0.0.0.0
- SEASONPACKARR__PORT=42069
- SEASONPACKARR__API_TOKEN=

# Logging settings
- SEASONPACKARR__LOG_LEVEL=DEBUG
- SEASONPACKARR__LOG_PATH=/config/logs/seasonpackarr.log
- SEASONPACKARR__LOG_MAX_SIZE=50
- SEASONPACKARR__LOG_MAX_BACKUPS=3

# Feature settings
- SEASONPACKARR__SMART_MODE=true
- SEASONPACKARR__SMART_MODE_THRESHOLD=0.75
- SEASONPACKARR__PARSE_TORRENT_FILE=true

# Fuzzy matching settings
- SEASONPACKARR__FUZZY_MATCHING_SKIP_REPACK_COMPARE=true
- SEASONPACKARR__FUZZY_MATCHING_SIMPLIFY_HDR_COMPARE=false

# Notification settings
- SEASONPACKARR__NOTIFICATIONS_DISCORD=
- SEASONPACKARR__NOTIFICATIONS_NOTIFICATION_LEVEL=MATCH,ERROR

# Client settings (can have multiple clients by changing DEFAULT to another name)
- SEASONPACKARR__CLIENTS_DEFAULT_HOST=127.0.0.1
- SEASONPACKARR__CLIENTS_DEFAULT_PORT=8080
- SEASONPACKARR__CLIENTS_DEFAULT_USERNAME=admin
- SEASONPACKARR__CLIENTS_DEFAULT_PASSWORD=adminadmin
- SEASONPACKARR__CLIENTS_DEFAULT_PREIMPORTPATH=/data/torrents/tv
volumes:
- ${DOCKERCONFDIR}/seasonpackarr:/config # location of the config file
- /data/torrents:/data/torrents # your torrent data directory
97 changes: 93 additions & 4 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,15 +290,25 @@ func New(configPath string, version string) *AppConfig {
c := &AppConfig{
m: new(sync.Mutex),
}
c.defaults()

c.Config = &domain.Config{
Version: version,
ConfigPath: configPath,
Clients: make(map[string]*domain.Client),
}

// check config file mode first
c.Config.DisableConfigFile = os.Getenv("SEASONPACKARR__DISABLE_CONFIG_FILE") == "true"

// load default configuration values if config file is enabled
if !c.Config.DisableConfigFile {
c.defaults()
c.load(configPath)
}

c.load(configPath)
c.loadFromEnv()

// validate client configuration regardless of source
for clientName, client := range c.Config.Clients {
if client.PreImportPath == "" {
log.Fatalf("preImportPath for client %q can't be empty, please provide a valid path to the directory you want seasonpacks to be hardlinked to", clientName)
Expand Down Expand Up @@ -333,11 +343,32 @@ func (c *AppConfig) defaults() {
func (c *AppConfig) loadFromEnv() {
prefix := "SEASONPACKARR__"

logLevel := "DEBUG"
if envLogLevel := os.Getenv(prefix + "LOG_LEVEL"); envLogLevel != "" {
logLevel = envLogLevel
}

// create a temporary logger with the detected log level
tempConfig := &domain.Config{
LogLevel: logLevel,
Version: "dev",
}
log := logger.New(tempConfig)

envs := os.Environ()
for _, env := range envs {
if strings.HasPrefix(env, prefix) {
envPair := strings.SplitN(env, "=", 2)

logValue := envPair[1]
if strings.Contains(envPair[0], "PASSWORD") ||
strings.Contains(envPair[0], "TOKEN") ||
strings.Contains(envPair[0], "API_KEY") {
logValue = "**REDACTED**"
}

log.Trace().Msgf("Processing environment variable: %s=%s", envPair[0], logValue)

if envPair[1] != "" {
switch envPair[0] {
case prefix + "HOST":
Expand All @@ -346,6 +377,9 @@ func (c *AppConfig) loadFromEnv() {
if i, _ := strconv.ParseInt(envPair[1], 10, 32); i > 0 {
c.Config.Port = int(i)
}
case prefix + "API_TOKEN":
c.Config.APIToken = envPair[1]

case prefix + "LOG_LEVEL":
c.Config.LogLevel = envPair[1]
case prefix + "LOG_PATH":
Expand All @@ -358,6 +392,7 @@ func (c *AppConfig) loadFromEnv() {
if i, _ := strconv.ParseInt(envPair[1], 10, 32); i > 0 {
c.Config.LogMaxBackups = int(i)
}

case prefix + "SMART_MODE":
if b, err := strconv.ParseBool(envPair[1]); err == nil {
c.Config.SmartMode = b
Expand All @@ -370,8 +405,58 @@ func (c *AppConfig) loadFromEnv() {
if b, err := strconv.ParseBool(envPair[1]); err == nil {
c.Config.ParseTorrentFile = b
}
case prefix + "API_TOKEN":
c.Config.APIToken = envPair[1]

case prefix + "FUZZY_MATCHING_SKIP_REPACK_COMPARE":
if b, err := strconv.ParseBool(envPair[1]); err == nil {
c.Config.FuzzyMatching.SkipRepackCompare = b
}
case prefix + "FUZZY_MATCHING_SIMPLIFY_HDR_COMPARE":
if b, err := strconv.ParseBool(envPair[1]); err == nil {
c.Config.FuzzyMatching.SimplifyHdrCompare = b
}

case prefix + "NOTIFICATIONS_DISCORD":
c.Config.Notifications.Discord = envPair[1]
case prefix + "NOTIFICATIONS_NOTIFICATION_LEVEL":
levels := strings.Split(envPair[1], ",")
for i, level := range levels {
levels[i] = strings.TrimSpace(level)
}
c.Config.Notifications.NotificationLevel = levels

case prefix + "DISABLE_CONFIG_FILE":
if b, err := strconv.ParseBool(envPair[1]); err == nil {
c.Config.DisableConfigFile = b
}
}

// client settings - handle dynamic client names
if strings.HasPrefix(envPair[0], prefix+"CLIENTS_") {
parts := strings.Split(strings.TrimPrefix(envPair[0], prefix+"CLIENTS_"), "_")
if len(parts) == 2 {
clientName := strings.ToLower(parts[0])
setting := parts[1]

// initialize client if it doesn't exist
if c.Config.Clients[clientName] == nil {
c.Config.Clients[clientName] = &domain.Client{}
}

switch setting {
case "HOST":
c.Config.Clients[clientName].Host = envPair[1]
case "PORT":
if i, _ := strconv.ParseInt(envPair[1], 10, 32); i > 0 {
c.Config.Clients[clientName].Port = int(i)
}
case "USERNAME":
c.Config.Clients[clientName].Username = envPair[1]
case "PASSWORD":
c.Config.Clients[clientName].Password = envPair[1]
case "PREIMPORTPATH":
c.Config.Clients[clientName].PreImportPath = envPair[1]
}
}
}
}
}
Expand Down Expand Up @@ -449,6 +534,10 @@ func (c *AppConfig) DynamicReload(log logger.Logger) {
}

func (c *AppConfig) UpdateConfig() error {
if c.Config.DisableConfigFile {
return nil
}

filePath := path.Join(c.Config.ConfigPath, "config.yaml")

f, err := os.ReadFile(filePath)
Expand Down
1 change: 1 addition & 0 deletions internal/domain/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ type Config struct {
FuzzyMatching FuzzyMatching `yaml:"fuzzyMatching"`
APIToken string `yaml:"apiToken"`
Notifications Notifications `yaml:"notifications"`
DisableConfigFile bool
}
Loading