616c6b1c62
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
494 lines
13 KiB
Go
494 lines
13 KiB
Go
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")
|
|
// }
|
|
// }
|