- Fix url escape for webdav files
- Add support for bind address, url base
This commit is contained in:
@@ -8,9 +8,11 @@ import (
|
||||
"github.com/sirrobot01/decypharr/pkg/qbit"
|
||||
"github.com/sirrobot01/decypharr/pkg/server"
|
||||
"github.com/sirrobot01/decypharr/pkg/service"
|
||||
"github.com/sirrobot01/decypharr/pkg/version"
|
||||
"github.com/sirrobot01/decypharr/pkg/web"
|
||||
"github.com/sirrobot01/decypharr/pkg/webdav"
|
||||
"github.com/sirrobot01/decypharr/pkg/worker"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
@@ -111,7 +113,7 @@ func startServices(ctx context.Context) error {
|
||||
+-------------------------------------------------------+
|
||||
| |
|
||||
| ╔╦╗╔═╗╔═╗╦ ╦╔═╗╦ ╦╔═╗╦═╗╦═╗ |
|
||||
| ║║║╣ ║ └┬┘╠═╝╠═╣╠═╣╠╦╝╠╦╝ |
|
||||
| ║║║╣ ║ └┬┘╠═╝╠═╣╠═╣╠╦╝╠╦╝ (%s) |
|
||||
| ═╩╝╚═╝╚═╝ ┴ ╩ ╩ ╩╩ ╩╩╚═╩╚═ |
|
||||
| |
|
||||
+-------------------------------------------------------+
|
||||
@@ -119,11 +121,10 @@ func startServices(ctx context.Context) error {
|
||||
+-------------------------------------------------------+
|
||||
`
|
||||
|
||||
_log.Info().Msgf(asciiArt, cfg.LogLevel)
|
||||
fmt.Printf(asciiArt, version.GetInfo(), cfg.LogLevel)
|
||||
|
||||
svc := service.New()
|
||||
_qbit := qbit.New()
|
||||
srv := server.New()
|
||||
_webdav := webdav.New()
|
||||
|
||||
ui := web.New(_qbit).Routes()
|
||||
@@ -131,9 +132,12 @@ func startServices(ctx context.Context) error {
|
||||
qbitRoutes := _qbit.Routes()
|
||||
|
||||
// Register routes
|
||||
srv.Mount("/", ui)
|
||||
srv.Mount("/api/v2", qbitRoutes)
|
||||
srv.Mount("/webdav", webdavRoutes)
|
||||
handlers := map[string]http.Handler{
|
||||
"/": ui,
|
||||
"/api/v2": qbitRoutes,
|
||||
"/webdav": webdavRoutes,
|
||||
}
|
||||
srv := server.New(handlers)
|
||||
|
||||
safeGo := func(f func() error) {
|
||||
wg.Add(1)
|
||||
|
||||
@@ -26,7 +26,7 @@ func main() {
|
||||
config.SetConfigPath(configPath)
|
||||
cfg := config.Get()
|
||||
// Get port from environment variable or use default
|
||||
qbitPort := getEnvOrDefault("QBIT_PORT", cfg.QBitTorrent.Port)
|
||||
port := getEnvOrDefault("QBIT_PORT", cfg.Port)
|
||||
webdavPath := ""
|
||||
for _, debrid := range cfg.Debrids {
|
||||
if debrid.UseWebDav {
|
||||
@@ -47,18 +47,18 @@ func main() {
|
||||
defer cancel()
|
||||
|
||||
// Check qBittorrent API
|
||||
if checkQbitAPI(ctx, qbitPort) {
|
||||
if checkQbitAPI(ctx, port) {
|
||||
status.QbitAPI = true
|
||||
}
|
||||
|
||||
// Check Web UI
|
||||
if checkWebUI(ctx, qbitPort) {
|
||||
if checkWebUI(ctx, port) {
|
||||
status.WebUI = true
|
||||
}
|
||||
|
||||
// Check WebDAV if enabled
|
||||
if webdavPath != "" {
|
||||
if checkWebDAV(ctx, qbitPort, webdavPath) {
|
||||
if checkWebDAV(ctx, port, webdavPath) {
|
||||
status.WebDAVService = true
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -34,7 +34,7 @@ type Debrid struct {
|
||||
type QBitTorrent struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Port string `json:"port,omitempty"`
|
||||
Port string `json:"port,omitempty"` // deprecated
|
||||
DownloadFolder string `json:"download_folder,omitempty"`
|
||||
Categories []string `json:"categories,omitempty"`
|
||||
RefreshInterval int `json:"refresh_interval,omitempty"`
|
||||
@@ -81,6 +81,11 @@ type WebDav struct {
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
// server
|
||||
BindAddress string `json:"bind_address,omitempty"`
|
||||
URLBase string `json:"url_base,omitempty"`
|
||||
Port string `json:"port,omitempty"`
|
||||
|
||||
LogLevel string `json:"log_level,omitempty"`
|
||||
Debrids []Debrid `json:"debrids,omitempty"`
|
||||
MaxCacheSize int `json:"max_cache_size,omitempty"`
|
||||
@@ -127,6 +132,16 @@ func (c *Config) loadConfig() error {
|
||||
c.AllowedExt = getDefaultExtensions()
|
||||
}
|
||||
|
||||
c.Port = cmp.Or(c.Port, c.QBitTorrent.Port)
|
||||
|
||||
if c.URLBase == "" {
|
||||
c.URLBase = "/"
|
||||
}
|
||||
// validate url base starts with /
|
||||
if c.URLBase[0] != '/' {
|
||||
c.URLBase = "/" + c.URLBase
|
||||
}
|
||||
|
||||
// Load the auth file
|
||||
c.Auth = c.GetAuth()
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/sirrobot01/decypharr/pkg/debrid/types"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -22,7 +23,8 @@ type fileInfo struct {
|
||||
isDir bool
|
||||
}
|
||||
|
||||
func (fi *fileInfo) Name() string { return fi.name }
|
||||
func (fi *fileInfo) Name() string { return url.PathEscape(fi.name) }
|
||||
func (fi *fileInfo) RawName() string { return fi.name }
|
||||
func (fi *fileInfo) Size() int64 { return fi.size }
|
||||
func (fi *fileInfo) Mode() os.FileMode { return fi.mode }
|
||||
func (fi *fileInfo) ModTime() time.Time { return fi.modTime }
|
||||
|
||||
@@ -25,7 +25,7 @@ type QBit struct {
|
||||
func New() *QBit {
|
||||
_cfg := config.Get()
|
||||
cfg := _cfg.QBitTorrent
|
||||
port := cmp.Or(cfg.Port, os.Getenv("QBIT_PORT"), "8282")
|
||||
port := cmp.Or(_cfg.Port, os.Getenv("QBIT_PORT"), "8282")
|
||||
refreshInterval := cmp.Or(cfg.RefreshInterval, 10)
|
||||
return &QBit{
|
||||
Username: cfg.Username,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -15,6 +14,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
path "path/filepath"
|
||||
"runtime"
|
||||
"syscall"
|
||||
)
|
||||
@@ -24,32 +24,47 @@ type Server struct {
|
||||
logger zerolog.Logger
|
||||
}
|
||||
|
||||
func New() *Server {
|
||||
func New(handlers map[string]http.Handler) *Server {
|
||||
l := logger.New("http")
|
||||
r := chi.NewRouter()
|
||||
r.Use(middleware.Recoverer)
|
||||
r.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
||||
|
||||
return &Server{
|
||||
router: r,
|
||||
cfg := config.Get()
|
||||
|
||||
s := &Server{
|
||||
logger: l,
|
||||
}
|
||||
|
||||
r.Handle(path.Join(cfg.URLBase, "static")+"/*",
|
||||
http.StripPrefix(path.Join(cfg.URLBase, "static"), http.FileServer(http.Dir("static"))),
|
||||
)
|
||||
|
||||
r.Route(cfg.URLBase, func(r chi.Router) {
|
||||
for pattern, handler := range handlers {
|
||||
r.Mount(pattern, handler)
|
||||
}
|
||||
|
||||
//logs
|
||||
r.Get("/logs", s.getLogs)
|
||||
|
||||
//stats
|
||||
r.Get("/stats", s.getStats)
|
||||
|
||||
//webhooks
|
||||
r.Post("/webhooks/tautulli", s.handleTautulli)
|
||||
|
||||
})
|
||||
s.router = r
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Server) Start(ctx context.Context) error {
|
||||
cfg := config.Get()
|
||||
// Register routes
|
||||
// Register webhooks
|
||||
s.router.Post("/webhooks/tautulli", s.handleTautulli)
|
||||
|
||||
// Register logs
|
||||
s.router.Get("/logs", s.getLogs)
|
||||
s.router.Get("/stats", s.getStats)
|
||||
p := cmp.Or(cfg.QBitTorrent.Port, "8282")
|
||||
port := fmt.Sprintf(":%s", p)
|
||||
s.logger.Info().Msgf("Server started on %s", port)
|
||||
addr := fmt.Sprintf("%s:%s", cfg.BindAddress, cfg.Port)
|
||||
s.logger.Info().Msgf("Starting server on %s%s", addr, cfg.URLBase)
|
||||
srv := &http.Server{
|
||||
Addr: port,
|
||||
Addr: addr,
|
||||
Handler: s.router,
|
||||
}
|
||||
|
||||
@@ -68,10 +83,6 @@ func (s *Server) Start(ctx context.Context) error {
|
||||
return srv.Shutdown(context.Background())
|
||||
}
|
||||
|
||||
func (s *Server) AddRoutes(routes func(r chi.Router) http.Handler) {
|
||||
routes(s.router)
|
||||
}
|
||||
|
||||
func (s *Server) Mount(pattern string, handler http.Handler) {
|
||||
s.router.Mount(pattern, handler)
|
||||
}
|
||||
|
||||
@@ -33,6 +33,42 @@
|
||||
Open Magnet Links in Decypharr
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mt-3">
|
||||
<div class="form-group">
|
||||
<label for="urlBase">URL Base</label>
|
||||
<input type="text"
|
||||
disabled
|
||||
class="form-control"
|
||||
id="urlBase"
|
||||
name="url_base"
|
||||
placeholder="/">
|
||||
<small class="form-text text-muted">URL base for the application</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mt-3">
|
||||
<div class="form-group">
|
||||
<label for="bindAddress">Bind Address</label>
|
||||
<input type="text"
|
||||
disabled
|
||||
class="form-control"
|
||||
id="bindAddress"
|
||||
name="bind_address"
|
||||
placeholder="">
|
||||
<small class="form-text text-muted">Bind address for the application(default is all interface)</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2 mt-3">
|
||||
<div class="form-group">
|
||||
<label for="port">Port</label>
|
||||
<input type="text"
|
||||
disabled
|
||||
class="form-control"
|
||||
id="port"
|
||||
name="port"
|
||||
placeholder="8282">
|
||||
<small class="form-text text-muted">Port</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mt-3">
|
||||
<div class="form-group">
|
||||
<label for="discordWebhookUrl">Discord Webhook URL</label>
|
||||
@@ -69,7 +105,6 @@
|
||||
<small class="form-text text-muted">Minimum file size to download (0 for no limit)</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mt-3">
|
||||
<div class="form-group">
|
||||
<label for="maxFileSize">Maximum File Size</label>
|
||||
@@ -106,10 +141,6 @@
|
||||
<label class="form-label" for="qbit.password">Password</label>
|
||||
<input type="password" class="form-control" name="qbit.password" id="qbit.password">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label" for="qbit.port">Port</label>
|
||||
<input type="text" class="form-control" name="qbit.port" id="qbit.port" placeholder="e.g., 8080">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label" for="qbit.download_folder">Symlink/Download Folder</label>
|
||||
<input type="text" class="form-control" name="qbit.download_folder" id="qbit.download_folder">
|
||||
@@ -369,6 +400,15 @@
|
||||
if (config.discord_webhook_url) {
|
||||
document.querySelector('[name="discord_webhook_url"]').value = config.discord_webhook_url;
|
||||
}
|
||||
if (config.url_base) {
|
||||
document.querySelector('[name="url_base"]').value = config.url_base;
|
||||
}
|
||||
if (config.bind_address) {
|
||||
document.querySelector('[name="bind_address"]').value = config.bind_address;
|
||||
}
|
||||
if (config.port) {
|
||||
document.querySelector('[name="port"]').value = config.port;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading configuration:', error);
|
||||
@@ -532,7 +572,6 @@
|
||||
qbittorrent: {
|
||||
username: document.querySelector('[name="qbit.username"]').value,
|
||||
password: document.querySelector('[name="qbit.password"]').value,
|
||||
port: document.querySelector('[name="qbit.port"]').value,
|
||||
download_folder: document.querySelector('[name="qbit.download_folder"]').value,
|
||||
refresh_interval: parseInt(document.querySelector('[name="qbit.refresh_interval"]').value || '0', 10)
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package webdav
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
@@ -14,7 +15,8 @@ type FileInfo struct {
|
||||
isDir bool
|
||||
}
|
||||
|
||||
func (fi *FileInfo) Name() string { return fi.name }
|
||||
func (fi *FileInfo) Name() string { return url.PathEscape(fi.name) }
|
||||
func (fi *FileInfo) RawName() string { return fi.name }
|
||||
func (fi *FileInfo) Size() int64 { return fi.size }
|
||||
func (fi *FileInfo) Mode() os.FileMode { return fi.mode }
|
||||
func (fi *FileInfo) ModTime() time.Time { return fi.modTime }
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
path "path/filepath"
|
||||
"slices"
|
||||
@@ -466,13 +465,6 @@ func (h *Handler) serveDirectory(w http.ResponseWriter, r *http.Request, file we
|
||||
"add": func(a, b int) int {
|
||||
return a + b
|
||||
},
|
||||
"urlpath": func(p string) string {
|
||||
segments := strings.Split(p, "/")
|
||||
for i, segment := range segments {
|
||||
segments[i] = url.PathEscape(segment)
|
||||
}
|
||||
return strings.Join(segments, "/")
|
||||
},
|
||||
"formatSize": func(bytes int64) string {
|
||||
const (
|
||||
KB = 1024
|
||||
|
||||
@@ -109,13 +109,13 @@ const directoryTemplate = `
|
||||
<h1>Index of {{.Path}}</h1>
|
||||
<ul>
|
||||
{{if .ShowParent}}
|
||||
<li><a href="{{urlpath .ParentPath}}" class="parent-dir"><span class="file-number"></span>Parent Directory</a></li>
|
||||
<li><a href="{{ .ParentPath}}" class="parent-dir"><span class="file-number"></span>Parent Directory</a></li>
|
||||
{{end}}
|
||||
{{range $index, $file := .Children}}
|
||||
<li>
|
||||
<a href="{{urlpath (printf "%s/%s" $.Path $file.Name)}}">
|
||||
<a href="{{(printf "%s/%s" $.Path $file.Name)}}">
|
||||
<span class="file-number">{{add $index 1}}.</span>
|
||||
<span class="file-name">{{$file.Name}}{{if $file.IsDir}}/{{end}}</span>
|
||||
<span class="file-name">{{$file.RawName}}{{if $file.IsDir}}/{{end}}</span>
|
||||
<span class="file-info">
|
||||
{{if not $file.IsDir}}
|
||||
{{formatSize $file.Size}}
|
||||
|
||||
Reference in New Issue
Block a user