Initial commit: docker compose config
Release Docker multi arch / docker (push) Has been cancelled
Test Install Script / Test Script Syntax (push) Has been cancelled
Test Install Script / Test on almalinux-10 (default) (push) Has been cancelled
Test Install Script / Test on almalinux-10 (root) (push) Has been cancelled
Test Install Script / Test on almalinux-8 (default) (push) Has been cancelled
Test Install Script / Test on almalinux-8 (root) (push) Has been cancelled
Test Install Script / Test on almalinux-9 (default) (push) Has been cancelled
Test Install Script / Test on almalinux-9 (root) (push) Has been cancelled
Test Install Script / Test on amazonlinux-2 (default) (push) Has been cancelled
Test Install Script / Test on amazonlinux-2 (root) (push) Has been cancelled
Test Install Script / Test on debian-11 (default) (push) Has been cancelled
Test Install Script / Test on debian-11 (root) (push) Has been cancelled
Test Install Script / Test on debian-12 (default) (push) Has been cancelled
Test Install Script / Test on debian-12 (root) (push) Has been cancelled
Test Install Script / Test on debian-13 (default) (push) Has been cancelled
Test Install Script / Test on debian-13 (root) (push) Has been cancelled
Test Install Script / Test on fedora-latest (default) (push) Has been cancelled
Test Install Script / Test on fedora-latest (root) (push) Has been cancelled
Test Install Script / Test on rocky-10 (default) (push) Has been cancelled
Test Install Script / Test on rocky-10 (root) (push) Has been cancelled
Test Install Script / Test on rocky-8 (default) (push) Has been cancelled
Test Install Script / Test on rocky-8 (root) (push) Has been cancelled
Test Install Script / Test on rocky-9 (default) (push) Has been cancelled
Test Install Script / Test on rocky-9 (root) (push) Has been cancelled
Test Install Script / Test on ubuntu-22.04 (default) (push) Has been cancelled
Test Install Script / Test on ubuntu-22.04 (root) (push) Has been cancelled
Test Install Script / Test on ubuntu-24.04 (default) (push) Has been cancelled
Test Install Script / Test on ubuntu-24.04 (root) (push) Has been cancelled
Release Docker multi arch / docker (push) Has been cancelled
Test Install Script / Test Script Syntax (push) Has been cancelled
Test Install Script / Test on almalinux-10 (default) (push) Has been cancelled
Test Install Script / Test on almalinux-10 (root) (push) Has been cancelled
Test Install Script / Test on almalinux-8 (default) (push) Has been cancelled
Test Install Script / Test on almalinux-8 (root) (push) Has been cancelled
Test Install Script / Test on almalinux-9 (default) (push) Has been cancelled
Test Install Script / Test on almalinux-9 (root) (push) Has been cancelled
Test Install Script / Test on amazonlinux-2 (default) (push) Has been cancelled
Test Install Script / Test on amazonlinux-2 (root) (push) Has been cancelled
Test Install Script / Test on debian-11 (default) (push) Has been cancelled
Test Install Script / Test on debian-11 (root) (push) Has been cancelled
Test Install Script / Test on debian-12 (default) (push) Has been cancelled
Test Install Script / Test on debian-12 (root) (push) Has been cancelled
Test Install Script / Test on debian-13 (default) (push) Has been cancelled
Test Install Script / Test on debian-13 (root) (push) Has been cancelled
Test Install Script / Test on fedora-latest (default) (push) Has been cancelled
Test Install Script / Test on fedora-latest (root) (push) Has been cancelled
Test Install Script / Test on rocky-10 (default) (push) Has been cancelled
Test Install Script / Test on rocky-10 (root) (push) Has been cancelled
Test Install Script / Test on rocky-8 (default) (push) Has been cancelled
Test Install Script / Test on rocky-8 (root) (push) Has been cancelled
Test Install Script / Test on rocky-9 (default) (push) Has been cancelled
Test Install Script / Test on rocky-9 (root) (push) Has been cancelled
Test Install Script / Test on ubuntu-22.04 (default) (push) Has been cancelled
Test Install Script / Test on ubuntu-22.04 (root) (push) Has been cancelled
Test Install Script / Test on ubuntu-24.04 (default) (push) Has been cancelled
Test Install Script / Test on ubuntu-24.04 (root) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,493 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"server/torr/utils"
|
||||
|
||||
"github.com/alexflint/go-arg"
|
||||
"github.com/pkg/browser"
|
||||
|
||||
"server"
|
||||
"server/docs"
|
||||
"server/log"
|
||||
"server/settings"
|
||||
"server/torr"
|
||||
"server/version"
|
||||
)
|
||||
|
||||
type args struct {
|
||||
Port string `arg:"-p" help:"web server port (default 8090)"`
|
||||
IP string `arg:"-i" help:"web server addr (default empty)"`
|
||||
Ssl bool `help:"enables https"`
|
||||
SslPort string `help:"web server ssl port, If not set, will be set to default 8091 or taken from db(if stored previously). Accepted if --ssl enabled."`
|
||||
SslCert string `help:"path to ssl cert file. If not set, will be taken from db(if stored previously) or default self-signed certificate/key will be generated. Accepted if --ssl enabled."`
|
||||
SslKey string `help:"path to ssl key file. If not set, will be taken from db(if stored previously) or default self-signed certificate/key will be generated. Accepted if --ssl enabled."`
|
||||
Path string `arg:"-d" help:"database and config dir path"`
|
||||
LogPath string `arg:"-l" help:"server log file path"`
|
||||
WebLogPath string `arg:"-w" help:"web access log file path"`
|
||||
RDB bool `arg:"-r" help:"start in read-only DB mode"`
|
||||
HttpAuth bool `arg:"-a" help:"enable http auth on all requests"`
|
||||
DontKill bool `arg:"-k" help:"don't kill server on signal"`
|
||||
UI bool `arg:"-u" help:"open torrserver page in browser"`
|
||||
TorrentsDir string `arg:"-t" help:"autoload torrents from dir"`
|
||||
TorrentAddr string `help:"Torrent client address, like 127.0.0.1:1337 (default :PeersListenPort)"`
|
||||
PubIPv4 string `arg:"-4" help:"set public IPv4 addr"`
|
||||
PubIPv6 string `arg:"-6" help:"set public IPv6 addr"`
|
||||
SearchWA bool `arg:"-s" help:"search without auth"`
|
||||
MaxSize string `arg:"-m" help:"max allowed stream size (in Bytes)"`
|
||||
TGToken string `arg:"-T" help:"telegram bot token"`
|
||||
FusePath string `arg:"-f" help:"fuse mount path"`
|
||||
WebDAV bool `help:"web dav enable"`
|
||||
ProxyURL string `help:"proxy URL for BitTorrent traffic (http, socks4, socks5, socks5h), e.g. socks5://user:password@127.0.0.1:8080"`
|
||||
ProxyMode string `help:"proxy mode: tracker (only HTTP trackers, default), peers (only peer connections), or full (all traffic)"`
|
||||
ForceHTTPS bool `arg:"--force-https" help:"redirect all HTTP requests to HTTPS (requires --ssl)"`
|
||||
}
|
||||
|
||||
func (args) Version() string {
|
||||
return "TorrServer " + version.Version
|
||||
}
|
||||
|
||||
var params args
|
||||
|
||||
func main() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
arg.MustParse(¶ms)
|
||||
|
||||
if params.Path == "" {
|
||||
params.Path, _ = os.Getwd()
|
||||
}
|
||||
|
||||
if params.Port == "" {
|
||||
params.Port = "8090"
|
||||
}
|
||||
|
||||
settings.Path = params.Path
|
||||
settings.HttpAuth = params.HttpAuth
|
||||
log.Init(params.LogPath, params.WebLogPath)
|
||||
|
||||
fmt.Println("=========== START ===========")
|
||||
fmt.Println("TorrServer", version.Version+",", runtime.Version()+",", "CPU Num:", runtime.NumCPU())
|
||||
if params.HttpAuth {
|
||||
log.TLogln("Use HTTP Auth file", settings.Path+"/accs.db")
|
||||
}
|
||||
if params.RDB {
|
||||
log.TLogln("Running in Read-only DB mode!")
|
||||
}
|
||||
docs.SwaggerInfo.Version = version.Version
|
||||
|
||||
// Simple Usage:
|
||||
dnsResolve()
|
||||
|
||||
// Advanced Usage:
|
||||
// config := DNSConfig{
|
||||
// PrimaryServers: []string{"1.1.1.1:53", "8.8.8.8:53"},
|
||||
// Timeout: 3 * time.Second,
|
||||
// }
|
||||
// checker := NewDNSChecker(config)
|
||||
// // Perform DNS lookup with automatic fallback
|
||||
// addrs, err := checker.LookupHostWithFallback("themoviedb.org")
|
||||
// if err != nil {
|
||||
// log.TLogln("DNS lookup failed:", err)
|
||||
// } else {
|
||||
// fmt.Println("DNS resolved:", addrs)
|
||||
// }
|
||||
|
||||
Preconfig(params.DontKill)
|
||||
|
||||
if params.UI {
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
if params.Ssl {
|
||||
browser.OpenURL("https://127.0.0.1:" + params.SslPort)
|
||||
} else {
|
||||
browser.OpenURL("http://127.0.0.1:" + params.Port)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if params.TorrentAddr != "" {
|
||||
settings.TorAddr = params.TorrentAddr
|
||||
}
|
||||
|
||||
if params.PubIPv4 != "" {
|
||||
settings.PubIPv4 = params.PubIPv4
|
||||
}
|
||||
|
||||
if params.PubIPv6 != "" {
|
||||
settings.PubIPv6 = params.PubIPv6
|
||||
}
|
||||
|
||||
if params.TorrentsDir != "" {
|
||||
go watchTDir(params.TorrentsDir)
|
||||
}
|
||||
|
||||
if params.MaxSize != "" {
|
||||
maxSize, err := strconv.ParseInt(params.MaxSize, 10, 64)
|
||||
if err == nil {
|
||||
settings.MaxSize = maxSize
|
||||
}
|
||||
}
|
||||
|
||||
if params.ProxyURL != "" && params.ProxyMode == "" {
|
||||
params.ProxyMode = "tracker" // default
|
||||
}
|
||||
if params.ProxyMode != "" && params.ProxyMode != "tracker" && params.ProxyMode != "peers" && params.ProxyMode != "full" {
|
||||
log.TLogln("Invalid proxy mode, using default 'tracker'")
|
||||
params.ProxyMode = "tracker"
|
||||
}
|
||||
|
||||
settings.Args = &settings.ExecArgs{
|
||||
Port: params.Port,
|
||||
IP: params.IP,
|
||||
Ssl: params.Ssl,
|
||||
SslPort: params.SslPort,
|
||||
SslCert: params.SslCert,
|
||||
SslKey: params.SslKey,
|
||||
Path: params.Path,
|
||||
LogPath: params.LogPath,
|
||||
WebLogPath: params.WebLogPath,
|
||||
RDB: params.RDB,
|
||||
HttpAuth: params.HttpAuth,
|
||||
DontKill: params.DontKill,
|
||||
UI: params.UI,
|
||||
TorrentsDir: params.TorrentsDir,
|
||||
TorrentAddr: params.TorrentAddr,
|
||||
PubIPv4: params.PubIPv4,
|
||||
PubIPv6: params.PubIPv6,
|
||||
SearchWA: params.SearchWA,
|
||||
MaxSize: params.MaxSize,
|
||||
TGToken: params.TGToken,
|
||||
FusePath: params.FusePath,
|
||||
WebDAV: params.WebDAV,
|
||||
ProxyURL: params.ProxyURL,
|
||||
ProxyMode: params.ProxyMode,
|
||||
ForceHTTPS: params.ForceHTTPS,
|
||||
}
|
||||
|
||||
if params.ProxyURL != "" {
|
||||
log.TLogln("Proxy configured from CLI:", params.ProxyURL, "mode:", settings.Args.ProxyMode)
|
||||
}
|
||||
|
||||
if params.ForceHTTPS && !params.Ssl {
|
||||
log.TLogln("Error: --force-https requires --ssl")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
server.Start()
|
||||
log.TLogln(server.WaitServer())
|
||||
log.Close()
|
||||
time.Sleep(time.Second * 3)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func watchTDir(dir string) {
|
||||
time.Sleep(5 * time.Second)
|
||||
path, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
path = dir
|
||||
}
|
||||
for {
|
||||
files, err := os.ReadDir(path)
|
||||
if err == nil {
|
||||
for _, file := range files {
|
||||
filename := filepath.Join(path, file.Name())
|
||||
if strings.ToLower(filepath.Ext(file.Name())) == ".torrent" {
|
||||
sp, err := utils.OpenTorrentFile(filename)
|
||||
if err == nil {
|
||||
tor, err := torr.AddTorrent(sp, "", "", "", "")
|
||||
if err == nil {
|
||||
if tor.GotInfo() {
|
||||
if tor.Title == "" {
|
||||
tor.Title = tor.Name()
|
||||
}
|
||||
torr.SaveTorrentToDB(tor)
|
||||
tor.Drop()
|
||||
os.Remove(filename)
|
||||
time.Sleep(time.Second)
|
||||
} else {
|
||||
log.TLogln("Error get info from torrent")
|
||||
}
|
||||
} else {
|
||||
log.TLogln("Error parse torrent file:", err)
|
||||
}
|
||||
} else {
|
||||
log.TLogln("Error parse file name:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.TLogln("Error read dir:", err)
|
||||
}
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
}
|
||||
|
||||
///////////
|
||||
/// DNS
|
||||
///
|
||||
|
||||
// DNSConfig holds DNS resolver configuration
|
||||
type DNSConfig struct {
|
||||
PrimaryServers []string
|
||||
FallbackServers []string
|
||||
Timeout time.Duration
|
||||
CacheDuration time.Duration
|
||||
}
|
||||
|
||||
// DefaultDNSConfig returns a sensible default configuration
|
||||
func DefaultDNSConfig() DNSConfig {
|
||||
return DNSConfig{
|
||||
PrimaryServers: []string{
|
||||
"8.8.8.8:53", // Google DNS
|
||||
"1.1.1.1:53", // CloudFlare DNS
|
||||
"9.9.9.9:53", // Quad9 DNS
|
||||
},
|
||||
FallbackServers: []string{
|
||||
"208.67.222.222:53", // OpenDNS
|
||||
"64.6.64.6:53", // Verisign
|
||||
},
|
||||
Timeout: 5 * time.Second,
|
||||
CacheDuration: 5 * time.Minute,
|
||||
}
|
||||
}
|
||||
|
||||
// DNSChecker manages DNS resolution with fallback support
|
||||
type DNSChecker struct {
|
||||
config DNSConfig
|
||||
customResolver *net.Resolver
|
||||
cache map[string][]string
|
||||
cacheTime map[string]time.Time
|
||||
mu sync.RWMutex
|
||||
useFallback bool
|
||||
}
|
||||
|
||||
// NewDNSChecker creates a new DNS checker instance
|
||||
func NewDNSChecker(config DNSConfig) *DNSChecker {
|
||||
if len(config.PrimaryServers) == 0 {
|
||||
config = DefaultDNSConfig()
|
||||
}
|
||||
|
||||
return &DNSChecker{
|
||||
config: config,
|
||||
cache: make(map[string][]string),
|
||||
cacheTime: make(map[string]time.Time),
|
||||
}
|
||||
}
|
||||
|
||||
// CheckAndResolve performs DNS check and returns a resolver
|
||||
func (d *DNSChecker) CheckAndResolve() *net.Resolver {
|
||||
// Test system DNS first
|
||||
if d.testSystemDNS() {
|
||||
log.TLogln("System DNS check passed")
|
||||
return net.DefaultResolver
|
||||
}
|
||||
|
||||
log.TLogln("System DNS check failed, using custom resolver")
|
||||
d.initCustomResolver()
|
||||
return d.customResolver
|
||||
}
|
||||
|
||||
// testSystemDNS checks if system DNS is working properly
|
||||
func (d *DNSChecker) testSystemDNS() bool {
|
||||
_, cancel := context.WithTimeout(context.Background(), d.config.Timeout)
|
||||
defer cancel()
|
||||
|
||||
addrs, err := net.LookupHost("themoviedb.org")
|
||||
if err != nil {
|
||||
log.TLogln("DNS lookup error:", err)
|
||||
return false
|
||||
}
|
||||
|
||||
if len(addrs) == 0 {
|
||||
log.TLogln("DNS lookup returned no addresses")
|
||||
return false
|
||||
}
|
||||
|
||||
// Check for suspicious addresses (DNS hijacking/pollution)
|
||||
for _, addr := range addrs {
|
||||
if isSuspiciousAddress(addr) {
|
||||
log.TLogln("Suspicious DNS address detected:", addr)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// isSuspiciousAddress checks if an address indicates DNS issues
|
||||
func isSuspiciousAddress(addr string) bool {
|
||||
suspiciousPrefixes := []string{
|
||||
"127.0.0.1", // Localhost
|
||||
"0.0.0.0", // Invalid address
|
||||
"::1", // IPv6 localhost
|
||||
// "10.", // Private network
|
||||
"192.168.", // Private network
|
||||
"169.254.", // Link-local
|
||||
// "172.16.", "172.17.", "172.18.", "172.19.",
|
||||
// "172.20.", "172.21.", "172.22.", "172.23.",
|
||||
// "172.24.", "172.25.", "172.26.", "172.27.",
|
||||
// "172.28.", "172.29.", "172.30.", "172.31.", // Private network range
|
||||
}
|
||||
|
||||
for _, prefix := range suspiciousPrefixes {
|
||||
if strings.HasPrefix(addr, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// initCustomResolver creates a custom resolver with fallback support
|
||||
func (d *DNSChecker) initCustomResolver() {
|
||||
d.customResolver = &net.Resolver{
|
||||
PreferGo: true, // Use Go's DNS implementation
|
||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
dialer := &net.Dialer{
|
||||
Timeout: d.config.Timeout,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}
|
||||
|
||||
// Try primary servers first
|
||||
for _, dns := range d.config.PrimaryServers {
|
||||
conn, err := dialer.DialContext(ctx, network, dns)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
log.TLogln("Failed to connect to DNS server", dns, ":", err)
|
||||
}
|
||||
|
||||
// Try fallback servers if primary fails
|
||||
for _, dns := range d.config.FallbackServers {
|
||||
conn, err := dialer.DialContext(ctx, network, dns)
|
||||
if err == nil {
|
||||
log.TLogln("Using fallback DNS server:", dns)
|
||||
return conn, nil
|
||||
}
|
||||
log.TLogln("Failed to connect to fallback DNS", dns, ":", err)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("all DNS servers failed")
|
||||
},
|
||||
}
|
||||
|
||||
d.useFallback = true
|
||||
}
|
||||
|
||||
// LookupHostWithFallback performs DNS lookup with automatic fallback
|
||||
func (d *DNSChecker) LookupHostWithFallback(host string) ([]string, error) {
|
||||
// Check cache first
|
||||
if addrs, ok := d.getFromCache(host); ok {
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
// Use appropriate resolver
|
||||
var resolver *net.Resolver
|
||||
if d.useFallback && d.customResolver != nil {
|
||||
resolver = d.customResolver
|
||||
} else {
|
||||
resolver = net.DefaultResolver
|
||||
}
|
||||
|
||||
// Perform lookup with timeout
|
||||
ctx, cancel := context.WithTimeout(context.Background(), d.config.Timeout)
|
||||
defer cancel()
|
||||
|
||||
addrs, err := resolver.LookupHost(ctx, host)
|
||||
if err != nil {
|
||||
// If using system DNS fails, try custom resolver
|
||||
if !d.useFallback && d.customResolver != nil {
|
||||
log.TLogln("System DNS failed, trying custom resolver")
|
||||
addrs, err = d.customResolver.LookupHost(ctx, host)
|
||||
}
|
||||
}
|
||||
|
||||
// Cache successful results
|
||||
if err == nil && len(addrs) > 0 {
|
||||
d.addToCache(host, addrs)
|
||||
}
|
||||
|
||||
return addrs, err
|
||||
}
|
||||
|
||||
// getFromCache retrieves DNS results from cache
|
||||
func (d *DNSChecker) getFromCache(host string) ([]string, bool) {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
|
||||
if addrs, ok := d.cache[host]; ok {
|
||||
if time.Since(d.cacheTime[host]) < d.config.CacheDuration {
|
||||
return addrs, true
|
||||
}
|
||||
// Expired, remove from cache
|
||||
delete(d.cache, host)
|
||||
delete(d.cacheTime, host)
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// addToCache adds DNS results to cache
|
||||
func (d *DNSChecker) addToCache(host string, addrs []string) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
d.cache[host] = addrs
|
||||
d.cacheTime[host] = time.Now()
|
||||
}
|
||||
|
||||
// Simple usage function (backward compatible)
|
||||
func dnsResolve() {
|
||||
checker := NewDNSChecker(DefaultDNSConfig())
|
||||
resolver := checker.CheckAndResolve()
|
||||
|
||||
// Store the resolver for later use if needed
|
||||
net.DefaultResolver = resolver // Optional: replace global resolver
|
||||
|
||||
// Test the resolver
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
addrs, err := resolver.LookupHost(ctx, "themoviedb.org")
|
||||
if err != nil {
|
||||
log.TLogln("DNS resolution test failed:", err)
|
||||
} else {
|
||||
log.TLogln("DNS resolution successful, addresses:", addrs)
|
||||
}
|
||||
}
|
||||
|
||||
// func dnsResolve() {
|
||||
// addrs, err := net.LookupHost("themoviedb.org")
|
||||
// if len(addrs) == 0 {
|
||||
// log.TLogln("System DNS check failed", err)
|
||||
|
||||
// fn := func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
// d := net.Dialer{}
|
||||
// return d.DialContext(ctx, "udp", "1.1.1.1:53")
|
||||
// }
|
||||
|
||||
// net.DefaultResolver = &net.Resolver{
|
||||
// Dial: fn,
|
||||
// }
|
||||
|
||||
// addrs, err = net.LookupHost("themoviedb.org")
|
||||
// if err != nil {
|
||||
// log.TLogln("Check CloudFlare DNS error:", err)
|
||||
// } else {
|
||||
// log.TLogln("Use CloudFlare DNS")
|
||||
// }
|
||||
// } else {
|
||||
// log.TLogln("System DNS check passed")
|
||||
// }
|
||||
// }
|
||||
Reference in New Issue
Block a user