Skip to content

Commit 2fe7421

Browse files
authored
Merge pull request #12 from Panonim/unstable
2 parents d4b113f + c61a7dc commit 2fe7421

File tree

2 files changed

+73
-9
lines changed

2 files changed

+73
-9
lines changed

README.md

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ services:
2222
PASSWORD: ${QB_PASSWORD}
2323
BASE_URL: ${QB_URL}
2424
AUTH_TOKEN: ${AUTH_TOKEN}
25-
LISTEN_PORT: "9911"
26-
RATE_LIMIT: "10" # API requests per minute (default: 10)
2725
restart: unless-stopped
26+
volumes:
27+
- ./logs:/app/logs # Optional
2828
```
2929
`.env`
3030
```env
@@ -33,6 +33,14 @@ QB_PASSWORD=
3333
QB_URL=http://IP:PORT
3434
AUTH_TOKEN=REPLACEME
3535
```
36+
`Optional Environment Variables`
37+
```yaml
38+
RATE_LIMIT: "10" # API requests per minute (default: 10)
39+
LOG_RETENTION_DAYS: 3 # 0 for purge-on-restart
40+
LOG_DIR: /app/logs # Where docker should save your logs
41+
DEBUG: true # Logs level
42+
LISTEN_PORT: "9911". # Port on which qBW should listen internally
43+
```
3644

3745
## Environment Variables
3846

@@ -41,21 +49,21 @@ You **must** provide these in a `.env` file or your environment:
4149
* `QB_URL` — base URL of your qBittorrent Web UI (e.g., `http://localhost:8080`)
4250
* `QB_USERNAME` — your qBittorrent username
4351
* `QB_PASSWORD` — your qBittorrent password
44-
* `AUTH_TOKEN` — Bearer token required to access the `/qb/torrents` endpoint
52+
* `AUTH_TOKEN` — This is not a qBittorrent token. It’s a bearer token used by qBWrapper to control access (like a password) to the /qb/torrents endpoint.
4553

4654
## Glance
4755
```yaml
4856
- type: custom-api
4957
title: qBittorrent
5058
cache: 15m
5159
options:
52-
always-show-stats: false
60+
always-show-stats: true
5361
subrequests:
5462
info:
55-
url: "http://${QB_URL}/qb/torrents"
63+
url: "http://${QBW_URL}/qb/torrents"
5664
method: GET
5765
headers:
58-
Authorization: "Bearer ${AUTH_TOKEN}" # your token
66+
Authorization: "Bearer ${AUTH_TOKEN}" # your QBW token
5967
template: |
6068
{{ $info := .Subrequest "info" }}
6169
{{ $torrents := $info.JSON.Array "" }}
@@ -161,8 +169,8 @@ You **must** provide these in a `.env` file or your environment:
161169

162170
### Glance env
163171
You can put this app in the same place as glance and the same .env file, but in case you are using it alone please put this in your `.env`.
164-
* `QB_URL` — base URL of your qBittorrent Web UI (e.g., `http://localhost:8080`)
165-
* `AUTH_TOKEN` — Bearer token required to access the `/qb/torrents` endpoin
172+
* `QBW_URL` — base URL of your qBittorrent Wrapper (e.g., `http://localhost:9911`)
173+
* `AUTH_TOKEN` — This is not a qBittorrent token. It’s a bearer token used by qBWrapper to control access (like a password) to the /qb/torrents endpoint.
166174

167175
## What you get in the response
168176

@@ -183,4 +191,3 @@ Each torrent object includes:
183191
* The cache is locked for concurrency safety.
184192
* If the qBittorrent login fails, the app exits.
185193
* If you hit the endpoint without a valid token, you get a 401 Unauthorized.
186-
* The app uses standard Go HTTP server and `github.com/joho/godotenv` for env loading.

app/main.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"strings"
1414
"sync"
1515
"time"
16+
"path/filepath"
1617
)
1718

1819
var (
@@ -351,16 +352,71 @@ func toFloat(i interface{}) float64 {
351352
return 0
352353
}
353354

355+
func cleanOldLogs(logDir string, retentionDays int) {
356+
if retentionDays == 0 {
357+
log.Println("LOG_RETENTION_DAYS=0; purging all logs")
358+
} else {
359+
log.Printf("Cleaning logs older than %d days in %s\n", retentionDays, logDir)
360+
}
361+
362+
now := time.Now()
363+
_ = filepath.Walk(logDir, func(path string, info os.FileInfo, err error) error {
364+
if err != nil || info.IsDir() {
365+
return nil
366+
}
367+
368+
if !strings.HasSuffix(info.Name(), ".log") {
369+
return nil
370+
}
371+
372+
age := now.Sub(info.ModTime())
373+
if retentionDays == 0 || age > (time.Duration(retentionDays)*24*time.Hour) {
374+
log.Printf("Removing old log: %s (age: %v)", path, age.Round(time.Second))
375+
os.Remove(path)
376+
}
377+
return nil
378+
})
379+
}
380+
354381
func main() {
355382
baseURL = strings.TrimRight(os.Getenv("BASE_URL"), "/")
356383
username = os.Getenv("USERNAME")
357384
password = os.Getenv("PASSWORD")
358385
authToken = os.Getenv("AUTH_TOKEN")
359386
debug = os.Getenv("DEBUG") == "true"
360387

388+
logDir := os.Getenv("LOG_DIR")
389+
if logDir == "" {
390+
logDir = "./logs"
391+
}
392+
if err := os.MkdirAll(logDir, 0755); err != nil {
393+
fmt.Printf("Failed to create log directory: %v\n", err)
394+
os.Exit(1)
395+
}
396+
397+
logFileName := filepath.Join(logDir, "app-"+time.Now().Format("2006-01-02_15-04-05")+".log")
398+
logFile, err := os.OpenFile(logFileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
399+
if err != nil {
400+
fmt.Printf("Failed to open log file: %v\n", err)
401+
os.Exit(1)
402+
}
403+
404+
log.SetOutput(io.MultiWriter(os.Stdout, logFile))
405+
361406
if baseURL == "" || username == "" || password == "" || authToken == "" {
362407
log.Fatal("Missing required environment variables")
363408
}
409+
410+
retention := 3
411+
if val := os.Getenv("LOG_RETENTION_DAYS"); val != "" {
412+
if parsed, err := strconv.Atoi(val); err == nil && parsed >= 0 {
413+
retention = parsed
414+
} else {
415+
log.Printf("Invalid LOG_RETENTION_DAYS: %s, defaulting to %d", val, retention)
416+
}
417+
}
418+
419+
cleanOldLogs(logDir, retention)
364420
initRateLimit()
365421

366422
if err := login(); err != nil {
@@ -371,6 +427,7 @@ func main() {
371427
if port == "" {
372428
port = "9911"
373429
}
430+
374431
http.HandleFunc("/qb/torrents", authMiddleware(rateLimitMiddleware(torrentsHandler)))
375432
log.Printf("Listening on :%s\n", port)
376433
log.Fatal(http.ListenAndServe(":"+port, nil))

0 commit comments

Comments
 (0)