diff --git a/.dockerignore b/.dockerignore index 23237a9..fc54896 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,3 +7,5 @@ docker-compose.yml *.magnet **.torrent torrents.json +**/dist/ +*.json diff --git a/.gitignore b/.gitignore index 5f95a7f..b8c9c79 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ dist/ tmp/** torrents.json logs/** +auth.json diff --git a/Dockerfile b/Dockerfile index f7eeed3..ba11eb1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,25 +31,34 @@ RUN --mount=type=cache,target=/go/pkg/mod \ # Stage 2: Create directory structure FROM alpine:3.19 as dirsetup -RUN mkdir -p /logs && \ - chmod 777 /logs && \ - touch /logs/decypharr.log && \ - chmod 666 /logs/decypharr.log +RUN mkdir -p /data/logs && \ + chmod 777 /data/logs && \ + touch /data/logs/decypharr.log && \ + chmod 666 /data/logs/decypharr.log # Stage 3: Final image FROM gcr.io/distroless/static-debian12:nonroot +LABEL version = "${VERSION}-${CHANNEL}" + +LABEL org.opencontainers.image.source = "https://github.com/sirrobot01/debrid-blackhole" +LABEL org.opencontainers.image.title = "debrid-blackhole" +LABEL org.opencontainers.image.authors = "sirrobot01" +LABEL org.opencontainers.image.documentation = "https://github.com/sirrobot01/debrid-blackhole/blob/main/README.md" + # Copy binaries COPY --from=builder --chown=nonroot:nonroot /blackhole /blackhole COPY --from=builder --chown=nonroot:nonroot /healthcheck /healthcheck # Copy pre-made directory structure -COPY --from=dirsetup --chown=nonroot:nonroot /logs /logs +COPY --from=dirsetup --chown=nonroot:nonroot /data /data # Metadata -ENV LOG_PATH=/logs +ENV LOG_PATH=/data/logs EXPOSE 8181 8282 -VOLUME ["/app"] +VOLUME ["/data", "/app"] USER nonroot:nonroot + HEALTHCHECK CMD ["/healthcheck"] -CMD ["/blackhole", "--config", "/app/config.json"] \ No newline at end of file + +CMD ["/blackhole", "--config", "/data"] \ No newline at end of file diff --git a/README.md b/README.md index 2dc83c3..ce8f416 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ services: user: "1000:1000" volumes: - /mnt/:/mnt - - ~/plex/configs/blackhole/config.json:/app/config.json # Config file, see below + - ~/plex/configs/blackhole/:/data # Path to the config file. config.json environment: - PUID=1000 - PGID=1000 @@ -78,7 +78,7 @@ services: Download the binary from the releases page and run it with the config file. ```bash -./blackhole --config /path/to/config.json +./blackhole --config /path/to/ ``` ### Usage @@ -104,7 +104,7 @@ Download the binary from the releases page and run it with the config file. #### Basic Sample Config -This is the default config file. You can create a `config.json` file in the root directory of the project or mount it in the docker-compose file. +This is the default config file. You can create a `config.json` file in the root directory of the project or mount it to /data in the docker-compose file. ```json { "debrids": [ @@ -145,6 +145,7 @@ Full config are [here](doc/config.full.json) - The `log_level` key is used to set the log level of the application. The default value is `info`. log level can be set to `debug`, `info`, `warn`, `error` - The `max_cache_size` key is used to set the maximum number of infohashes that can be stored in the availability cache. This is used to prevent round trip to the debrid provider when using the proxy/Qbittorrent. The default value is `1000` - The `allowed_file_types` key is an array of allowed file types that can be downloaded. By default, all movie, tv show and music file types are allowed +- `use_auth` is used to enable basic authentication for the UI. ##### Debrid Config - The `debrids` key is an array of debrid providers diff --git a/cmd/main.go b/cmd/main.go index 26f3bf7..e54251d 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -3,11 +3,13 @@ package cmd import ( "context" "github.com/sirrobot01/debrid-blackhole/internal/config" + "github.com/sirrobot01/debrid-blackhole/internal/logger" "github.com/sirrobot01/debrid-blackhole/pkg/arr" "github.com/sirrobot01/debrid-blackhole/pkg/debrid" "github.com/sirrobot01/debrid-blackhole/pkg/proxy" "github.com/sirrobot01/debrid-blackhole/pkg/qbit" "github.com/sirrobot01/debrid-blackhole/pkg/repair" + "github.com/sirrobot01/debrid-blackhole/pkg/version" "log" "sync" ) @@ -15,6 +17,13 @@ import ( func Start(ctx context.Context) error { cfg := config.GetConfig() + _log := logger.GetLogger(cfg.LogLevel) + + _log.Debug().Msgf("Config Loaded: %s", cfg.JsonFile()) + _log.Debug().Msgf("Default Log Level: %s", cfg.LogLevel) + + _log.Info().Msgf("Version: %s", version.GetInfo().String()) + deb := debrid.NewDebrid() arrs := arr.NewStorage() _repair := repair.NewRepair(deb.Get(), arrs) diff --git a/doc/config.full.json b/doc/config.full.json index ac175b0..5aa5024 100644 --- a/doc/config.full.json +++ b/doc/config.full.json @@ -75,5 +75,6 @@ "log_level": "info", "min_file_size": "", "max_file_size": "", - "allowed_file_types": [] + "allowed_file_types": [], + "use_auth": false } \ No newline at end of file diff --git a/go.mod b/go.mod index 7a16ee9..b4379bc 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/sirrobot01/debrid-blackhole -go 1.22 +go 1.23 + +toolchain go1.23.2 require ( github.com/anacrolix/torrent v1.55.0 @@ -12,6 +14,7 @@ require ( github.com/rs/zerolog v1.33.0 github.com/valyala/fasthttp v1.55.0 github.com/valyala/fastjson v1.6.4 + golang.org/x/crypto v0.33.0 golang.org/x/time v0.8.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) @@ -23,6 +26,8 @@ require ( github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/google/go-cmp v0.6.0 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect + github.com/gorilla/sessions v1.4.0 // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -32,6 +37,6 @@ require ( github.com/stretchr/testify v1.10.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect golang.org/x/net v0.33.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect ) diff --git a/go.sum b/go.sum index a2c8c1d..e4fff34 100644 --- a/go.sum +++ b/go.sum @@ -115,6 +115,10 @@ github.com/gopherjs/gopherjs v0.0.0-20190309154008-847fc94819f9/go.mod h1:wJfORR github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= +github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= @@ -219,6 +223,10 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -259,10 +267,14 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/internal/config/config.go b/internal/config/config.go index d67b40c..57a6926 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "os" + "path/filepath" "sync" ) @@ -57,6 +58,11 @@ type Repair struct { SkipDeletion bool `json:"skip_deletion"` } +type Auth struct { + Username string `json:"username"` + Password string `json:"password"` +} + type Config struct { LogLevel string `json:"log_level"` Debrid Debrid `json:"debrid"` @@ -69,6 +75,16 @@ type Config struct { AllowedExt []string `json:"allowed_file_types"` MinFileSize string `json:"min_file_size"` // Minimum file size to download, 10MB, 1GB, etc MaxFileSize string `json:"max_file_size"` // Maximum file size to download (0 means no limit) + Path string `json:"-"` // Path to save the config file + UseAuth bool `json:"use_auth"` + Auth *Auth `json:"-"` +} + +func (c *Config) JsonFile() string { + return filepath.Join(c.Path, "config.json") +} +func (c *Config) AuthFile() string { + return filepath.Join(c.Path, "auth.json") } func (c *Config) loadConfig() error { @@ -76,7 +92,8 @@ func (c *Config) loadConfig() error { if configPath == "" { return fmt.Errorf("config path not set") } - file, err := os.ReadFile(configPath) + c.Path = configPath + file, err := os.ReadFile(c.JsonFile()) if err != nil { return err } @@ -93,6 +110,9 @@ func (c *Config) loadConfig() error { c.AllowedExt = getDefaultExtensions() } + // Load the auth file + c.Auth = c.GetAuth() + // Validate the config //if err := validateConfig(c); err != nil { // return nil, err @@ -178,6 +198,12 @@ func validateConfig(config *Config) error { } func SetConfigPath(path string) { + // Backward compatibility + // Check if the path is not a dir + if fi, err := os.Stat(path); err == nil && !fi.IsDir() { + // Get the directory of the file + path = filepath.Dir(path) + } configPath = path } @@ -227,3 +253,35 @@ func (c *Config) IsSizeAllowed(size int64) bool { } return true } + +func (c *Config) GetAuth() *Auth { + if !c.UseAuth { + return nil + } + if c.Auth == nil { + c.Auth = &Auth{} + if _, err := os.Stat(c.AuthFile()); err == nil { + file, err := os.ReadFile(c.AuthFile()) + if err == nil { + _ = json.Unmarshal(file, c.Auth) + } + } + } + return c.Auth +} + +func (c *Config) SaveAuth(auth *Auth) error { + c.Auth = auth + data, err := json.Marshal(auth) + if err != nil { + return err + } + return os.WriteFile(c.AuthFile(), data, 0644) +} + +func (c *Config) NeedsSetup() bool { + if c.UseAuth { + return c.GetAuth().Username == "" + } + return false +} diff --git a/internal/logger/logger.go b/internal/logger/logger.go index 24990a3..03d2ff5 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -7,6 +7,12 @@ import ( "os" "path/filepath" "strings" + "sync" +) + +var ( + once sync.Once + logger zerolog.Logger ) func GetLogPath() string { @@ -78,3 +84,10 @@ func NewLogger(prefix string, level string, output *os.File) zerolog.Logger { } return logger } + +func GetLogger(level string) zerolog.Logger { + once.Do(func() { + logger = NewLogger("Decypharr", level, os.Stdout) + }) + return logger +} diff --git a/pkg/qbit/server/templates/layout.html b/pkg/qbit/server/templates/layout.html index 6a55317..f63be29 100644 --- a/pkg/qbit/server/templates/layout.html +++ b/pkg/qbit/server/templates/layout.html @@ -112,6 +112,11 @@ {{ template "repair" . }} {{ else if eq .Page "config" }} {{ template "config" . }} + {{ else if eq .Page "login" }} + {{ template "login" . }} + {{ else if eq .Page "setup" }} + {{ template "setup" . }} + {{ else }} {{ end }} diff --git a/pkg/qbit/server/templates/login.html b/pkg/qbit/server/templates/login.html new file mode 100644 index 0000000..1625f3b --- /dev/null +++ b/pkg/qbit/server/templates/login.html @@ -0,0 +1,131 @@ +{{ define "login" }} +