feat: add support for proxies to use with IRC and Indexers (#1421)

* feat: add support for proxies

* fix(http): release handler

* fix(migrations): define proxy early

* fix(migrations): pg proxy

* fix(proxy): list update delete

* fix(proxy): remove log and imports

* feat(irc): use proxy

* feat(irc): tests

* fix(web): update imports for ProxyForms.tsx

* fix(database): migration

* feat(proxy): test

* feat(proxy): validate proxy type

* feat(proxy): validate and test

* feat(proxy): improve validate and test

* feat(proxy): fix db schema

* feat(proxy): add db tests

* feat(proxy): handle http errors

* fix(http): imports

* feat(proxy): use proxy for indexer downloads

* feat(proxy): indexerforms select proxy

* feat(proxy): handle torrent download

* feat(proxy): skip if disabled

* feat(proxy): imports

* feat(proxy): implement in Feeds

* feat(proxy): update helper text indexer proxy

* feat(proxy): add internal cache
This commit is contained in:
ze0s 2024-09-02 11:10:45 +02:00 committed by GitHub
parent 472d327308
commit bc0f4cc055
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
59 changed files with 2533 additions and 371 deletions

View file

@ -6,6 +6,8 @@ package irc
import (
"crypto/tls"
"fmt"
"golang.org/x/net/proxy"
"net/url"
"slices"
"strings"
"time"
@ -220,6 +222,37 @@ func (h *Handler) Run() (err error) {
Log: subLogger,
}
if h.network.UseProxy && h.network.Proxy != nil {
if !h.network.Proxy.Enabled {
h.log.Debug().Msgf("proxy disabled, skip")
} else {
if h.network.Proxy.Addr == "" {
return errors.New("proxy addr missing")
}
proxyUrl, err := url.Parse(h.network.Proxy.Addr)
if err != nil {
return errors.Wrap(err, "could not parse proxy url: %s", h.network.Proxy.Addr)
}
// set user and pass if not empty
if h.network.Proxy.User != "" && h.network.Proxy.Pass != "" {
proxyUrl.User = url.UserPassword(h.network.Proxy.User, h.network.Proxy.Pass)
}
proxyDialer, err := proxy.FromURL(proxyUrl, proxy.Direct)
if err != nil {
return errors.Wrap(err, "could not create proxy dialer from url: %s", h.network.Proxy.Addr)
}
proxyContextDialer, ok := proxyDialer.(proxy.ContextDialer)
if !ok {
return errors.Wrap(err, "proxy dialer does not expose DialContext(): %v", proxyDialer)
}
client.DialContext = proxyContextDialer.DialContext
}
}
if h.network.Auth.Mechanism == domain.IRCAuthMechanismSASLPlain {
if h.network.Auth.Account != "" && h.network.Auth.Password != "" {
client.SASLLogin = h.network.Auth.Account

View file

@ -14,6 +14,7 @@ import (
"github.com/autobrr/autobrr/internal/indexer"
"github.com/autobrr/autobrr/internal/logger"
"github.com/autobrr/autobrr/internal/notification"
"github.com/autobrr/autobrr/internal/proxy"
"github.com/autobrr/autobrr/internal/release"
"github.com/autobrr/autobrr/pkg/errors"
@ -47,8 +48,10 @@ type service struct {
releaseService release.Service
indexerService indexer.Service
notificationService notification.Service
indexerMap map[string]string
handlers map[int64]*Handler
proxyService proxy.Service
indexerMap map[string]string
handlers map[int64]*Handler
stopWG sync.WaitGroup
lock sync.RWMutex
@ -56,7 +59,7 @@ type service struct {
const sseMaxEntries = 1000
func NewService(log logger.Logger, sse *sse.Server, repo domain.IrcRepo, releaseSvc release.Service, indexerSvc indexer.Service, notificationSvc notification.Service) Service {
func NewService(log logger.Logger, sse *sse.Server, repo domain.IrcRepo, releaseSvc release.Service, indexerSvc indexer.Service, notificationSvc notification.Service, proxySvc proxy.Service) Service {
return &service{
log: log.With().Str("module", "irc").Logger(),
sse: sse,
@ -64,6 +67,7 @@ func NewService(log logger.Logger, sse *sse.Server, repo domain.IrcRepo, release
releaseService: releaseSvc,
indexerService: indexerSvc,
notificationService: notificationSvc,
proxyService: proxySvc,
handlers: make(map[int64]*Handler),
}
}
@ -79,6 +83,15 @@ func (s *service) StartHandlers() {
continue
}
if network.ProxyId != 0 {
networkProxy, err := s.proxyService.FindByID(context.Background(), network.ProxyId)
if err != nil {
s.log.Error().Err(err).Msgf("failed to get proxy for network: %s", network.Server)
return
}
network.Proxy = networkProxy
}
channels, err := s.repo.ListChannels(network.ID)
if err != nil {
s.log.Error().Err(err).Msgf("failed to list channels for network: %s", network.Server)
@ -215,6 +228,14 @@ func (s *service) checkIfNetworkRestartNeeded(network *domain.IrcNetwork) error
restartNeeded = true
fieldsChanged = append(fieldsChanged, "bot mode")
}
if handler.UseProxy != network.UseProxy {
restartNeeded = true
fieldsChanged = append(fieldsChanged, "use proxy")
}
if handler.ProxyId != network.ProxyId {
restartNeeded = true
fieldsChanged = append(fieldsChanged, "proxy id")
}
if handler.Auth.Mechanism != network.Auth.Mechanism {
restartNeeded = true
fieldsChanged = append(fieldsChanged, "auth mechanism")
@ -476,12 +497,16 @@ func (s *service) GetNetworksWithHealth(ctx context.Context) ([]domain.IrcNetwor
BouncerAddr: n.BouncerAddr,
UseBouncer: n.UseBouncer,
BotMode: n.BotMode,
UseProxy: n.UseProxy,
ProxyId: n.ProxyId,
Connected: false,
Channels: []domain.ChannelWithHealth{},
ConnectionErrors: []string{},
}
s.lock.RLock()
handler, ok := s.handlers[n.ID]
s.lock.RUnlock()
if ok {
handler.ReportStatus(&netw)
}
@ -566,6 +591,18 @@ func (s *service) UpdateNetwork(ctx context.Context, network *domain.IrcNetwork)
}
s.log.Debug().Msgf("irc.service: update network: %s", network.Name)
network.Proxy = nil
// attach proxy
if network.UseProxy && network.ProxyId != 0 {
networkProxy, err := s.proxyService.FindByID(context.Background(), network.ProxyId)
if err != nil {
s.log.Error().Err(err).Msgf("failed to get proxy for network: %s", network.Server)
return errors.Wrap(err, "could not get proxy for network: %s", network.Server)
}
network.Proxy = networkProxy
}
// stop or start network
// TODO get current state to see if enabled or not?
if network.Enabled {