Features:
- Add file logging, server - Fix minor repair bug - Wrap up beta
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ docker-compose.yml
|
|||||||
dist/
|
dist/
|
||||||
tmp/**
|
tmp/**
|
||||||
torrents.json
|
torrents.json
|
||||||
|
logs/**
|
||||||
|
|||||||
@@ -117,7 +117,10 @@
|
|||||||
|
|
||||||
- Add support for multiple debrid providers
|
- Add support for multiple debrid providers
|
||||||
- A full-fledged UI for adding torrents, repairing files, viewing config and managing torrents
|
- A full-fledged UI for adding torrents, repairing files, viewing config and managing torrents
|
||||||
- Add a more robust logging system
|
|
||||||
- Fix issues with Alldebrid
|
- Fix issues with Alldebrid
|
||||||
- Fix file transversal bug
|
- Fix file transversal bug
|
||||||
- Fix files with no parent directory
|
- Fix files with no parent directory
|
||||||
|
- Logging
|
||||||
|
- Add a more robust logging system
|
||||||
|
- Add logging to a file
|
||||||
|
- Add logging to the UI
|
||||||
@@ -21,8 +21,9 @@ FROM scratch
|
|||||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
COPY --from=builder /blackhole /blackhole
|
COPY --from=builder /blackhole /blackhole
|
||||||
COPY --from=builder /app/README.md /README.md
|
COPY --from=builder /app/README.md /README.md
|
||||||
|
ENV LOG_PATH=/app/logs
|
||||||
|
|
||||||
EXPOSE 8181
|
EXPOSE 8181 8282
|
||||||
|
|
||||||
VOLUME ["/app"]
|
VOLUME ["/app"]
|
||||||
|
|
||||||
|
|||||||
26
README.md
26
README.md
@@ -4,6 +4,27 @@
|
|||||||
|
|
||||||
This is a Golang implementation go Torrent QbitTorrent with a **Multiple Debrid service support**.
|
This is a Golang implementation go Torrent QbitTorrent with a **Multiple Debrid service support**.
|
||||||
|
|
||||||
|
### Table of Contents
|
||||||
|
|
||||||
|
- [Features](#features)
|
||||||
|
- [Supported Debrid Providers](#supported-debrid-providers)
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Docker Compose](#docker-compose)
|
||||||
|
- [Binary](#binary)
|
||||||
|
- [Usage](#usage)
|
||||||
|
- [Connecting to Sonarr/Radarr](#connecting-to-sonarrradarr)
|
||||||
|
- [Sample Config](#sample-config)
|
||||||
|
- [Config Notes](#config-notes)
|
||||||
|
- [Log Level](#log-level)
|
||||||
|
- [Max Cache Size](#max-cache-size)
|
||||||
|
- [Debrid Config](#debrid-config)
|
||||||
|
- [Proxy Config](#proxy-config)
|
||||||
|
- [Qbittorrent Config](#qbittorrent-config)
|
||||||
|
- [Arrs Config](#arrs-config)
|
||||||
|
- [Proxy](#proxy)
|
||||||
|
- [Repair Worker](#repair-worker)
|
||||||
|
- [Changelog](#changelog)
|
||||||
|
- [TODO](#todo)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
@@ -25,7 +46,8 @@ The proxy is useful in filtering out un-cached Real Debrid torrents
|
|||||||
- [All Debrid](https://alldebrid.com)
|
- [All Debrid](https://alldebrid.com)
|
||||||
|
|
||||||
|
|
||||||
#### Installation
|
### Installation
|
||||||
|
|
||||||
##### Docker Compose
|
##### Docker Compose
|
||||||
```yaml
|
```yaml
|
||||||
version: '3.7'
|
version: '3.7'
|
||||||
@@ -38,7 +60,7 @@ services:
|
|||||||
- "8181:8181" # Proxy
|
- "8181:8181" # Proxy
|
||||||
user: "1000:1000"
|
user: "1000:1000"
|
||||||
volumes:
|
volumes:
|
||||||
- ./logs:/app/logs
|
- ./logs/:/app/logs
|
||||||
- /mnt/:/mnt
|
- /mnt/:/mnt
|
||||||
- ~/plex/configs/blackhole/config.json:/app/config.json # Config file, see below
|
- ~/plex/configs/blackhole/config.json:/app/config.json # Config file, see below
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -3,12 +3,37 @@ package common
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
"gopkg.in/natefinch/lumberjack.v2"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func GetLogPath() string {
|
||||||
|
logsDir := os.Getenv("LOG_PATH")
|
||||||
|
if logsDir == "" {
|
||||||
|
// Create the logs directory if it doesn't exist
|
||||||
|
logsDir = "logs"
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(logsDir, 0755); err != nil {
|
||||||
|
panic(fmt.Sprintf("Failed to create logs directory: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Join(logsDir, "decypharr.log")
|
||||||
|
}
|
||||||
|
|
||||||
func NewLogger(prefix string, level string, output *os.File) zerolog.Logger {
|
func NewLogger(prefix string, level string, output *os.File) zerolog.Logger {
|
||||||
writer := zerolog.ConsoleWriter{
|
|
||||||
|
rotatingLogFile := &lumberjack.Logger{
|
||||||
|
Filename: GetLogPath(),
|
||||||
|
MaxSize: 10,
|
||||||
|
MaxBackups: 2,
|
||||||
|
MaxAge: 28,
|
||||||
|
Compress: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
consoleWriter := zerolog.ConsoleWriter{
|
||||||
Out: output,
|
Out: output,
|
||||||
TimeFormat: "2006-01-02 15:04:05",
|
TimeFormat: "2006-01-02 15:04:05",
|
||||||
NoColor: false, // Set to true if you don't want colors
|
NoColor: false, // Set to true if you don't want colors
|
||||||
@@ -20,7 +45,21 @@ func NewLogger(prefix string, level string, output *os.File) zerolog.Logger {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
logger := zerolog.New(writer).
|
fileWriter := zerolog.ConsoleWriter{
|
||||||
|
Out: rotatingLogFile,
|
||||||
|
TimeFormat: "2006-01-02 15:04:05",
|
||||||
|
NoColor: true, // No colors in file output
|
||||||
|
FormatLevel: func(i interface{}) string {
|
||||||
|
return strings.ToUpper(fmt.Sprintf("| %-6s|", i))
|
||||||
|
},
|
||||||
|
FormatMessage: func(i interface{}) string {
|
||||||
|
return fmt.Sprintf("[%s] %v", prefix, i)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
multi := zerolog.MultiLevelWriter(consoleWriter, fileWriter)
|
||||||
|
|
||||||
|
logger := zerolog.New(multi).
|
||||||
With().
|
With().
|
||||||
Timestamp().
|
Timestamp().
|
||||||
Logger().
|
Logger().
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -29,4 +29,5 @@ require (
|
|||||||
golang.org/x/net v0.27.0 // indirect
|
golang.org/x/net v0.27.0 // indirect
|
||||||
golang.org/x/sys v0.22.0 // indirect
|
golang.org/x/sys v0.22.0 // indirect
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/text v0.16.0 // indirect
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -288,6 +288,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
|||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|||||||
@@ -45,9 +45,6 @@ func (q *qbitHandler) Routes(r chi.Router) http.Handler {
|
|||||||
|
|
||||||
func (u *uiHandler) Routes(r chi.Router) http.Handler {
|
func (u *uiHandler) Routes(r chi.Router) http.Handler {
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
//if u.debug {
|
|
||||||
// r.Use(middleware.Logger)
|
|
||||||
//}
|
|
||||||
r.Get("/", u.IndexHandler)
|
r.Get("/", u.IndexHandler)
|
||||||
r.Get("/download", u.DownloadHandler)
|
r.Get("/download", u.DownloadHandler)
|
||||||
r.Get("/repair", u.RepairHandler)
|
r.Get("/repair", u.RepairHandler)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/sirrobot01/debrid-blackhole/pkg/arr"
|
"github.com/sirrobot01/debrid-blackhole/pkg/arr"
|
||||||
"github.com/sirrobot01/debrid-blackhole/pkg/debrid"
|
"github.com/sirrobot01/debrid-blackhole/pkg/debrid"
|
||||||
"github.com/sirrobot01/debrid-blackhole/pkg/qbit/shared"
|
"github.com/sirrobot01/debrid-blackhole/pkg/qbit/shared"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@@ -41,6 +42,7 @@ func (s *Server) Start(ctx context.Context) error {
|
|||||||
ui := uiHandler{qbit: s.qbit, logger: common.NewLogger("UI", s.logger.GetLevel().String(), os.Stdout), debug: debug}
|
ui := uiHandler{qbit: s.qbit, logger: common.NewLogger("UI", s.logger.GetLevel().String(), os.Stdout), debug: debug}
|
||||||
|
|
||||||
// Register routes
|
// Register routes
|
||||||
|
r.Get("/logs", s.GetLogs)
|
||||||
q.Routes(r)
|
q.Routes(r)
|
||||||
ui.Routes(r)
|
ui.Routes(r)
|
||||||
|
|
||||||
@@ -67,3 +69,29 @@ func (s *Server) Start(ctx context.Context) error {
|
|||||||
s.logger.Info().Msg("Shutting down gracefully...")
|
s.logger.Info().Msg("Shutting down gracefully...")
|
||||||
return srv.Shutdown(context.Background())
|
return srv.Shutdown(context.Background())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) GetLogs(w http.ResponseWriter, r *http.Request) {
|
||||||
|
logFile := common.GetLogPath()
|
||||||
|
|
||||||
|
// Open and read the file
|
||||||
|
file, err := os.Open(logFile)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Error reading log file", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// Set headers
|
||||||
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
w.Header().Set("Content-Disposition", "inline; filename=application.log")
|
||||||
|
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
w.Header().Set("Pragma", "no-cache")
|
||||||
|
w.Header().Set("Expires", "0")
|
||||||
|
|
||||||
|
// Stream the file
|
||||||
|
_, err = io.Copy(w, file)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Error streaming log file", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,12 +22,12 @@
|
|||||||
<th>Progress</th>
|
<th>Progress</th>
|
||||||
<th>Speed</th>
|
<th>Speed</th>
|
||||||
<th>Category</th>
|
<th>Category</th>
|
||||||
|
<th>Debrid</th>
|
||||||
<th>State</th>
|
<th>State</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="torrentsList">
|
<tbody id="torrentsList">
|
||||||
<!-- Will be populated by JavaScript -->
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -51,6 +51,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>${formatSpeed(torrent.dlspeed)}</td>
|
<td>${formatSpeed(torrent.dlspeed)}</td>
|
||||||
<td><span class="badge bg-secondary">${torrent.category || 'None'}</span></td>
|
<td><span class="badge bg-secondary">${torrent.category || 'None'}</span></td>
|
||||||
|
<td><span class="badge bg-secondary">${torrent.debrid || 'None'}</span></td>
|
||||||
<td><span class="badge ${getStateColor(torrent.state)}">${torrent.state}</span></td>
|
<td><span class="badge ${getStateColor(torrent.state)}">${torrent.state}</span></td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-sm btn-outline-danger" onclick="deleteTorrent('${torrent.hash}')">
|
<button class="btn btn-sm btn-outline-danger" onclick="deleteTorrent('${torrent.hash}')">
|
||||||
|
|||||||
@@ -87,6 +87,11 @@
|
|||||||
<i class="bi bi-gear me-1"></i>Config
|
<i class="bi bi-gear me-1"></i>Config
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/logs" target="_blank">
|
||||||
|
<i class="bi bi-journal me-1"></i>Logs
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="badge me-2" id="channel-badge">Loading...</span>
|
<span class="badge me-2" id="channel-badge">Loading...</span>
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ type TorrentCategory struct {
|
|||||||
type Torrent struct {
|
type Torrent struct {
|
||||||
ID string `json:"-"`
|
ID string `json:"-"`
|
||||||
DebridTorrent *debrid.Torrent `json:"-"`
|
DebridTorrent *debrid.Torrent `json:"-"`
|
||||||
|
Debrid string `json:"debrid"`
|
||||||
TorrentPath string `json:"-"`
|
TorrentPath string `json:"-"`
|
||||||
|
|
||||||
AddedOn int64 `json:"added_on,omitempty"`
|
AddedOn int64 `json:"added_on,omitempty"`
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ func (q *QBit) UpdateTorrentMin(t *Torrent, debridTorrent *debrid.Torrent) *Torr
|
|||||||
t.Name = debridTorrent.Name
|
t.Name = debridTorrent.Name
|
||||||
t.AddedOn = addedOn.Unix()
|
t.AddedOn = addedOn.Unix()
|
||||||
t.DebridTorrent = debridTorrent
|
t.DebridTorrent = debridTorrent
|
||||||
|
t.Debrid = debridTorrent.Debrid.GetName()
|
||||||
t.Size = totalSize
|
t.Size = totalSize
|
||||||
t.Completed = sizeCompleted
|
t.Completed = sizeCompleted
|
||||||
t.Downloaded = sizeCompleted
|
t.Downloaded = sizeCompleted
|
||||||
|
|||||||
Reference in New Issue
Block a user