Skip to content
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
19 changes: 18 additions & 1 deletion examples/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ services:
# You need at least this one volume mapped so Unpackerr can find your files to extract.
# Make sure this matches your Starr apps; the folder mount (/downloads or /data) should be identical.
- /mnt/HostDownloads:/downloads
# OPTIONAL: If using mTLS certificates, mount the certificate directory.
# - /path/to/certs:/certs:ro
restart: always
# Get the user:group correct so unpackerr can read and write to your files.
user: ${PUID}:${PGID}
Expand Down Expand Up @@ -62,6 +64,9 @@ services:
- UN_SONARR_0_DELETE_DELAY=5m
- UN_SONARR_0_DELETE_ORIG=false
- UN_SONARR_0_SYNCTHING=false
- UN_SONARR_0_TLS_CLIENT_CERT=/path/to/client.crt
- UN_SONARR_0_TLS_CLIENT_KEY=/path/to/client.key
- UN_SONARR_0_TLS_CA_CERT=/path/to/ca-bundle.crt
## Radarr Settings
- UN_RADARR_0_URL=http://radarr:7878
- UN_RADARR_0_API_KEY=0123456789abcdef0123456789abcdef
Expand All @@ -71,6 +76,9 @@ services:
- UN_RADARR_0_DELETE_DELAY=5m
- UN_RADARR_0_DELETE_ORIG=false
- UN_RADARR_0_SYNCTHING=false
- UN_RADARR_0_TLS_CLIENT_CERT=/path/to/client.crt
- UN_RADARR_0_TLS_CLIENT_KEY=/path/to/client.key
- UN_RADARR_0_TLS_CA_CERT=/path/to/ca-bundle.crt
## Lidarr Settings
- UN_LIDARR_0_URL=http://lidarr:8686
- UN_LIDARR_0_API_KEY=0123456789abcdef0123456789abcdef
Expand All @@ -80,6 +88,9 @@ services:
- UN_LIDARR_0_DELETE_DELAY=5m
- UN_LIDARR_0_DELETE_ORIG=false
- UN_LIDARR_0_SYNCTHING=false
- UN_LIDARR_0_TLS_CLIENT_CERT=/path/to/client.crt
- UN_LIDARR_0_TLS_CLIENT_KEY=/path/to/client.key
- UN_LIDARR_0_TLS_CA_CERT=/path/to/ca-bundle.crt
## Readarr Settings
- UN_READARR_0_URL=http://readarr:8787
- UN_READARR_0_API_KEY=0123456789abcdef0123456789abcdef
Expand All @@ -89,6 +100,9 @@ services:
- UN_READARR_0_DELETE_DELAY=5m
- UN_READARR_0_DELETE_ORIG=false
- UN_READARR_0_SYNCTHING=false
- UN_READARR_0_TLS_CLIENT_CERT=/path/to/client.crt
- UN_READARR_0_TLS_CLIENT_KEY=/path/to/client.key
- UN_READARR_0_TLS_CA_CERT=/path/to/ca-bundle.crt
## Whisparr Settings
- UN_WHISPARR_0_URL=http://whisparr:6969
- UN_WHISPARR_0_API_KEY=0123456789abcdef0123456789abcdef
Expand All @@ -98,6 +112,9 @@ services:
- UN_WHISPARR_0_DELETE_DELAY=5m
- UN_WHISPARR_0_DELETE_ORIG=false
- UN_WHISPARR_0_SYNCTHING=false
- UN_WHISPARR_0_TLS_CLIENT_CERT=/path/to/client.crt
- UN_WHISPARR_0_TLS_CLIENT_KEY=/path/to/client.key
- UN_WHISPARR_0_TLS_CA_CERT=/path/to/ca-bundle.crt
## Watch Folders
- UN_FOLDER_0_PATH=/downloads/auto_extract
- UN_FOLDER_0_EXTRACT_PATH=
Expand Down Expand Up @@ -136,4 +153,4 @@ services:
- UN_CMDHOOK_0_EXCLUDE_1=lidarr
- UN_CMDHOOK_0_TIMEOUT=10s

## => Content Auto Generated, 12 APR 2025 04:54 UTC
## => Content Auto Generated, 28 AUG 2025 20:00 UTC
62 changes: 61 additions & 1 deletion examples/unpackerr.conf.example
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,18 @@ dir_mode = "0755"
# delete_orig = false
## If you use Syncthing, setting this to true will make unpackerr wait for syncs to finish.
# syncthing = false
## Path to TLS client certificate file for mutual TLS authentication.
## Required when your Starr app is behind a reverse proxy with mTLS enabled.
## Must be paired with tls_client_key. Leave empty for standard TLS connections.
# tls_client_cert = "/path/to/client.crt"
## Path to TLS client private key file for mutual TLS authentication.
## This key must match the tls_client_cert certificate.
## Both cert and key are required for mTLS to work.
# tls_client_key = "/path/to/client.key"
## Path to custom Certificate Authority bundle to verify server certificate.
## Use this when your Starr app uses a certificate signed by a private CA.
## If not set, system default CA bundle is used.
# tls_ca_cert = "/path/to/ca-bundle.crt"

## Leaving the [[radarr]] header uncommented (no leading hash #) without also
## uncommenting the api_key (remove the hash #) will produce a startup warning.
Expand All @@ -153,6 +165,18 @@ dir_mode = "0755"
# delete_orig = false
## If you use Syncthing, setting this to true will make unpackerr wait for syncs to finish.
# syncthing = false
## Path to TLS client certificate file for mutual TLS authentication.
## Required when your Starr app is behind a reverse proxy with mTLS enabled.
## Must be paired with tls_client_key. Leave empty for standard TLS connections.
# tls_client_cert = "/path/to/client.crt"
## Path to TLS client private key file for mutual TLS authentication.
## This key must match the tls_client_cert certificate.
## Both cert and key are required for mTLS to work.
# tls_client_key = "/path/to/client.key"
## Path to custom Certificate Authority bundle to verify server certificate.
## Use this when your Starr app uses a certificate signed by a private CA.
## If not set, system default CA bundle is used.
# tls_ca_cert = "/path/to/ca-bundle.crt"

#[[lidarr]]
# url = "http://127.0.0.1:8686"
Expand All @@ -172,6 +196,18 @@ dir_mode = "0755"
# delete_orig = false
## If you use Syncthing, setting this to true will make unpackerr wait for syncs to finish.
# syncthing = false
## Path to TLS client certificate file for mutual TLS authentication.
## Required when your Starr app is behind a reverse proxy with mTLS enabled.
## Must be paired with tls_client_key. Leave empty for standard TLS connections.
# tls_client_cert = "/path/to/client.crt"
## Path to TLS client private key file for mutual TLS authentication.
## This key must match the tls_client_cert certificate.
## Both cert and key are required for mTLS to work.
# tls_client_key = "/path/to/client.key"
## Path to custom Certificate Authority bundle to verify server certificate.
## Use this when your Starr app uses a certificate signed by a private CA.
## If not set, system default CA bundle is used.
# tls_ca_cert = "/path/to/ca-bundle.crt"

#[[readarr]]
# url = "http://127.0.0.1:8787"
Expand All @@ -191,6 +227,18 @@ dir_mode = "0755"
# delete_orig = false
## If you use Syncthing, setting this to true will make unpackerr wait for syncs to finish.
# syncthing = false
## Path to TLS client certificate file for mutual TLS authentication.
## Required when your Starr app is behind a reverse proxy with mTLS enabled.
## Must be paired with tls_client_key. Leave empty for standard TLS connections.
# tls_client_cert = "/path/to/client.crt"
## Path to TLS client private key file for mutual TLS authentication.
## This key must match the tls_client_cert certificate.
## Both cert and key are required for mTLS to work.
# tls_client_key = "/path/to/client.key"
## Path to custom Certificate Authority bundle to verify server certificate.
## Use this when your Starr app uses a certificate signed by a private CA.
## If not set, system default CA bundle is used.
# tls_ca_cert = "/path/to/ca-bundle.crt"

#[[whisparr]]
# url = "http://127.0.0.1:6969"
Expand All @@ -210,6 +258,18 @@ dir_mode = "0755"
# delete_orig = false
## If you use Syncthing, setting this to true will make unpackerr wait for syncs to finish.
# syncthing = false
## Path to TLS client certificate file for mutual TLS authentication.
## Required when your Starr app is behind a reverse proxy with mTLS enabled.
## Must be paired with tls_client_key. Leave empty for standard TLS connections.
# tls_client_cert = "/path/to/client.crt"
## Path to TLS client private key file for mutual TLS authentication.
## This key must match the tls_client_cert certificate.
## Both cert and key are required for mTLS to work.
# tls_client_key = "/path/to/client.key"
## Path to custom Certificate Authority bundle to verify server certificate.
## Use this when your Starr app uses a certificate signed by a private CA.
## If not set, system default CA bundle is used.
# tls_ca_cert = "/path/to/ca-bundle.crt"

##################################################################################
### ### STOP HERE ### STOP HERE ### STOP HERE ### STOP HERE #### STOP HERE ### #
Expand Down Expand Up @@ -302,4 +362,4 @@ dir_mode = "0755"
## You can adjust how long to wait for the command to run.
# timeout = "10s"

## => Content Auto Generated, 12 APR 2025 04:54 UTC
## => Content Auto Generated, 28 AUG 2025 20:00 UTC
2 changes: 2 additions & 0 deletions init/config/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ services:
# You need at least this one volume mapped so Unpackerr can find your files to extract.
# Make sure this matches your Starr apps; the folder mount (/downloads or /data) should be identical.
- /mnt/HostDownloads:/downloads
# OPTIONAL: If using mTLS certificates, mount the certificate directory.
# - /path/to/certs:/certs:ro
restart: always
# Get the user:group correct so unpackerr can read and write to your files.
user: ${PUID}:${PGID}
Expand Down
27 changes: 27 additions & 0 deletions init/config/definitions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,33 @@ sections:
recommend: *BOOLEAN
short: Setting this to true makes unpackerr wait for syncthing to finish.
desc: If you use Syncthing, setting this to true will make unpackerr wait for syncs to finish.
- name: tls_client_cert
envvar: TLS_CLIENT_CERT
default: ''
example: /path/to/client.crt
short: Path to TLS client certificate for mTLS.
desc: |
Path to TLS client certificate file for mutual TLS authentication.
Required when your Starr app is behind a reverse proxy with mTLS enabled.
Must be paired with tls_client_key. Leave empty for standard TLS connections.
- name: tls_client_key
envvar: TLS_CLIENT_KEY
default: ''
example: /path/to/client.key
short: Path to TLS client private key for mTLS.
desc: |
Path to TLS client private key file for mutual TLS authentication.
This key must match the tls_client_cert certificate.
Both cert and key are required for mTLS to work.
- name: tls_ca_cert
envvar: TLS_CA_CERT
default: ''
example: /path/to/ca-bundle.crt
short: Path to custom CA certificate.
desc: |
Path to custom Certificate Authority bundle to verify server certificate.
Use this when your Starr app uses a certificate signed by a private CA.
If not set, system default CA bundle is used.

# Global folder configuration.
folders:
Expand Down
1 change: 1 addition & 0 deletions pkg/unpackerr/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const (
var (
ErrInvalidURL = errors.New("provided application URL is invalid")
ErrInvalidKey = fmt.Errorf("provided application API Key is invalid, must be %d characters", apiKeyLength)
ErrInvalidCA = errors.New("failed parsing CA certificate")
)

// Config defines the configuration data used to start the application.
Expand Down
51 changes: 50 additions & 1 deletion pkg/unpackerr/cnfgfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package unpackerr

import (
"crypto/tls"
"crypto/x509"
"fmt"
"net/http"
"os"
Expand Down Expand Up @@ -311,12 +312,60 @@ func (u *Unpackerr) validateApp(conf *StarrConfig, app starr.App) error {
conf.Protocols = defaultProtocol
}

// Configure TLS and HTTP client
tlsConfig, err := u.configureTLS(conf, app)
if err != nil {
return err
}

conf.Config.Client = &http.Client{
Timeout: conf.Timeout.Duration,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: !conf.ValidSSL}, //nolint:gosec
TLSClientConfig: tlsConfig,
},
}

return nil
}

// configureTLS creates and configures the TLS config for mTLS support.
func (u *Unpackerr) configureTLS(conf *StarrConfig, app starr.App) (*tls.Config, error) {
// Create TLS config - default behavior unchanged
tlsConfig := &tls.Config{InsecureSkipVerify: !conf.ValidSSL} //nolint:gosec

// Add mTLS if certificates are configured
if conf.TLSClientCert != "" && conf.TLSClientKey != "" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if conf.TLSClientCert != "" && conf.TLSClientKey != "" {
if conf.TLSClientCert != "" || conf.TLSClientKey != "" {

I usually do an "or" on these two variables when dealing with SSL. In case a user sets one, they'll get an error. Instead of nothing.

certPath := expandHomedir(conf.TLSClientCert)
keyPath := expandHomedir(conf.TLSClientKey)

cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return nil, fmt.Errorf("%s (%s) failed loading TLS client cert from %s and %s: %w",
app, conf.URL, certPath, keyPath, err)
}

tlsConfig.Certificates = []tls.Certificate{cert}

u.Debugf("%s (%s): Loaded mTLS client certificate", app, conf.URL)
}

// Add custom CA if configured
if conf.TLSCACert != "" {
caCert, err := os.ReadFile(expandHomedir(conf.TLSCACert))
if err != nil {
return nil, fmt.Errorf("%s (%s) failed reading CA cert from %s: %w",
app, conf.URL, expandHomedir(conf.TLSCACert), err)
}

caCertPool := x509.NewCertPool()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
caCertPool := x509.NewCertPool()
tlsConfig.RootCA = x509.NewCertPool()

Can you just set this directly here? (I'm not sure if .RootCAs is an interface or not.)

if !caCertPool.AppendCertsFromPEM(caCert) {
return nil, fmt.Errorf("%w: %s (%s) from %s", ErrInvalidCA, app, conf.URL, expandHomedir(conf.TLSCACert))
}

tlsConfig.RootCAs = caCertPool

u.Debugf("%s (%s): Loaded custom CA certificate", app, conf.URL)
}

return tlsConfig, nil
}
20 changes: 12 additions & 8 deletions pkg/unpackerr/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,19 @@ type Extract struct {

// Shared config items for all starr apps.
type StarrConfig struct {
Path string `json:"path" toml:"path" xml:"path" yaml:"path"`
Paths StringSlice `json:"paths" toml:"paths" xml:"paths" yaml:"paths"`
Protocols string `json:"protocols" toml:"protocols" xml:"protocols" yaml:"protocols"`
DeleteOrig bool `json:"delete_orig" toml:"delete_orig" xml:"delete_orig" yaml:"delete_orig"`
DeleteDelay cnfg.Duration `json:"delete_delay" toml:"delete_delay" xml:"delete_delay" yaml:"delete_delay"`
Syncthing bool `json:"syncthing" toml:"syncthing" xml:"syncthing" yaml:"syncthing"`
ValidSSL bool `json:"valid_ssl" toml:"valid_ssl" xml:"valid_ssl" yaml:"valid_ssl"`
Timeout cnfg.Duration `json:"timeout" toml:"timeout" xml:"timeout" yaml:"timeout"`
starr.Config

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra whitespace you don't need here.

Path string `json:"path" toml:"path" xml:"path" yaml:"path"`
Paths StringSlice `json:"paths" toml:"paths" xml:"paths" yaml:"paths"`
Protocols string `json:"protocols" toml:"protocols" xml:"protocols" yaml:"protocols"`
DeleteOrig bool `json:"delete_orig" toml:"delete_orig" xml:"delete_orig" yaml:"delete_orig"`
DeleteDelay cnfg.Duration `json:"delete_delay" toml:"delete_delay" xml:"delete_delay" yaml:"delete_delay"`
Syncthing bool `json:"syncthing" toml:"syncthing" xml:"syncthing" yaml:"syncthing"`
ValidSSL bool `json:"valid_ssl" toml:"valid_ssl" xml:"valid_ssl" yaml:"valid_ssl"`
Timeout cnfg.Duration `json:"timeout" toml:"timeout" xml:"timeout" yaml:"timeout"`
TLSClientCert string `json:"tls_client_cert" toml:"tls_client_cert" xml:"tls_client_cert" yaml:"tls_client_cert"` //nolint:lll
TLSClientKey string `json:"tls_client_key" toml:"tls_client_key" xml:"tls_client_key" yaml:"tls_client_key"`
TLSCACert string `json:"tls_ca_cert" toml:"tls_ca_cert" xml:"tls_ca_cert" yaml:"tls_ca_cert"`
}
Comment on lines +42 to 45
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you consider this instead? You'll have to make a few other changes, but I think this will be a bit cleaner in the end.

Suggested change
TLSClientCert string `json:"tls_client_cert" toml:"tls_client_cert" xml:"tls_client_cert" yaml:"tls_client_cert"` //nolint:lll
TLSClientKey string `json:"tls_client_key" toml:"tls_client_key" xml:"tls_client_key" yaml:"tls_client_key"`
TLSCACert string `json:"tls_ca_cert" toml:"tls_ca_cert" xml:"tls_ca_cert" yaml:"tls_ca_cert"`
}
TLS *ClientTLS `json:"tls" toml:"tls" xml:"tls" yaml:"tls"`
}
type ClientTLS struct {
Cert string `json:"client_cert" toml:"client_cert" xml:"client_cert" yaml:"client_cert"`
Key string `json:"client_key" toml:"client_key" xml:"client_key" yaml:"client_key"`
CA string `json:"ca_cert" toml:"ca_cert" xml:"ca_cert" yaml:"ca_cert"`
}


// checkQueueChanges checks each item for state changes from the app queues.
Expand Down