This commit is contained in:
2025-11-03 15:12:03 +02:00
parent c361cc7b94
commit 9a90f2e6f3
8 changed files with 298 additions and 3 deletions

View File

@@ -1,3 +1,5 @@
//go:build !tray
package main
import (

220
server/cmd/main_tray.go Normal file
View File

@@ -0,0 +1,220 @@
//go:build windows && tray
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"sync/atomic"
"github.com/alexflint/go-arg"
"fyne.io/systray"
"golang.org/x/sys/windows/registry"
"server"
"server/log"
"server/settings"
)
var (
trayParams args
serverRunning int32
defaultLogPath string
startItem *systray.MenuItem
stopItem *systray.MenuItem
autostartItem *systray.MenuItem
)
// local copy of args used by tray build (original is behind !tray build tag)
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"`
JlfnAddr string `help:"Jellyfin .strm files path (e.g., /media/jellyfin/metadata)"`
JlfnSrv string `help:"Jellyfin server URL (e.g., http://127.0.0.1:8096)"`
JlfnApi string `help:"Jellyfin API key"`
JlfnAutoCreate bool `help:"Auto-create .strm files when adding torrents via web"`
TMDBApiKey string `help:"TMDB API key for metadata and posters"`
TorrServerHost string `help:"Public TorrServer URL for .strm files (e.g., http://192.168.1.197:5665)"`
}
func main() {
arg.MustParse(&trayParams)
if trayParams.Path == "" {
trayParams.Path, _ = os.Getwd()
}
// Ensure logs go to file so we can tail in separate terminal
defaultLogPath = trayParams.LogPath
if defaultLogPath == "" {
defaultLogPath = filepath.Join(trayParams.Path, "torrserver.log")
trayParams.LogPath = defaultLogPath
}
settings.Path = trayParams.Path
settings.HttpAuth = trayParams.HttpAuth
log.Init(trayParams.LogPath, trayParams.WebLogPath)
// Start tray UI
systray.Run(onReady, onExit)
}
func onReady() {
// Try to load icon from common locations
exeDir := func() string { p, _ := os.Executable(); d, _ := filepath.Abs(filepath.Dir(p)); return d }()
iconCandidates := []string{
filepath.Join(exeDir, "favicon.ico"),
filepath.Join(exeDir, "..", "web", "public", "favicon.ico"),
filepath.Join(exeDir, "..", "..", "web", "public", "favicon.ico"),
}
for _, p := range iconCandidates {
if b, err := os.ReadFile(p); err == nil && len(b) > 0 {
systray.SetIcon(b)
break
}
}
systray.SetTitle("TorrServer")
systray.SetTooltip("TorrServer Jellyfin")
startItem = systray.AddMenuItem("Start server", "Start server")
stopItem = systray.AddMenuItem("Stop server", "Stop server")
autostartItem = systray.AddMenuItemCheckbox("Autostart", "Run at user login", isAutostartEnabled())
logsItem := systray.AddMenuItem("Logs", "Open logs window")
systray.AddSeparator()
quitItem := systray.AddMenuItem("Exit", "Exit tray")
go func() {
for {
select {
case <-startItem.ClickedCh:
startServer()
case <-stopItem.ClickedCh:
stopServer()
case <-autostartItem.ClickedCh:
desired := !isAutostartEnabled()
setAutostart(desired)
refreshMenuState()
case <-logsItem.ClickedCh:
openLogsTerminal()
case <-quitItem.ClickedCh:
stopServer()
systray.Quit()
return
}
}
}()
// Sync initial state and auto start server when tray UI is ready
refreshMenuState()
startServer()
}
func refreshMenuState() {
if atomic.LoadInt32(&serverRunning) == 1 {
if startItem != nil { startItem.Disable() }
if stopItem != nil { stopItem.Enable() }
} else {
if startItem != nil { startItem.Enable() }
if stopItem != nil { stopItem.Disable() }
}
if autostartItem != nil {
if isAutostartEnabled() { autostartItem.Check() } else { autostartItem.Uncheck() }
}
}
func onExit() {
log.Close()
}
func startServer() {
if !atomic.CompareAndSwapInt32(&serverRunning, 0, 1) {
return
}
// Minimal defaults matching non-tray main
if trayParams.Port == "" { trayParams.Port = "8090" }
// Run server in goroutine
go func() {
server.Start(trayParams.Port, trayParams.IP, trayParams.SslPort, trayParams.SslCert, trayParams.SslKey, trayParams.Ssl, trayParams.RDB, trayParams.SearchWA, trayParams.TGToken, trayParams.JlfnAddr, trayParams.JlfnSrv, trayParams.JlfnApi, trayParams.JlfnAutoCreate, trayParams.TMDBApiKey, trayParams.TorrServerHost)
_ = server.WaitServer()
atomic.StoreInt32(&serverRunning, 0)
refreshMenuState()
}()
refreshMenuState()
}
func stopServer() {
if !atomic.CompareAndSwapInt32(&serverRunning, 1, 0) {
return
}
server.Stop()
refreshMenuState()
}
func openLogsTerminal() {
// Prefer file tail if we have a file path
logPath := defaultLogPath
if logPath == "" {
// fallback to simple echo to keep window open
_ = exec.Command("cmd", "/c", "start", "", "powershell", "-NoExit", "-Command", "Write-Host 'No log file configured. Set --logpath' ").Start()
return
}
// Ensure file exists so Get-Content -Wait won't fail immediately
if _, err := os.Stat(logPath); os.IsNotExist(err) {
_ = os.MkdirAll(filepath.Dir(logPath), 0o755)
if f, err := os.Create(logPath); err == nil { f.Close() }
}
psCmd := fmt.Sprintf("Get-Content -Path '%s' -Wait -Encoding UTF8", strings.ReplaceAll(logPath, "'", "''"))
// Try PowerShell 7 first (pwsh), then Windows PowerShell
if err := exec.Command("pwsh", "-NoExit", "-Command", psCmd).Start(); err != nil {
_ = exec.Command("powershell", "-NoExit", "-Command", psCmd).Start()
}
}
func isAutostartEnabled() bool {
key, err := registry.OpenKey(registry.CURRENT_USER, `Software\\Microsoft\\Windows\\CurrentVersion\\Run`, registry.QUERY_VALUE)
if err != nil {
return false
}
defer key.Close()
_, _, err = key.GetStringValue("TorrServer")
return err == nil
}
func setAutostart(enable bool) {
key, _, err := registry.CreateKey(registry.CURRENT_USER, `Software\\Microsoft\\Windows\\CurrentVersion\\Run`, registry.SET_VALUE|registry.QUERY_VALUE)
if err != nil {
return
}
defer key.Close()
if enable {
exe, _ := os.Executable()
exe, _ = filepath.Abs(exe)
cmd := fmt.Sprintf("\"%s\" --tray", exe)
_ = key.SetStringValue("TorrServer", cmd)
} else {
_ = key.DeleteValue("TorrServer")
}
}

Binary file not shown.

View File

@@ -10,6 +10,7 @@ replace (
)
require (
fyne.io/systray v1.11.0
github.com/agnivade/levenshtein v1.2.1
github.com/alexflint/go-arg v1.5.0
github.com/anacrolix/dms v1.7.1
@@ -31,6 +32,7 @@ require (
go.etcd.io/bbolt v1.4.0
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476
golang.org/x/image v0.28.0
golang.org/x/sys v0.33.0
golang.org/x/time v0.12.0
gopkg.in/telebot.v4 v4.0.0-beta.5
gopkg.in/vansante/go-ffprobe.v2 v2.2.1
@@ -75,6 +77,7 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.26.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
@@ -96,7 +99,6 @@ require (
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/tools v0.34.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

View File

@@ -58,6 +58,8 @@ crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg=
fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
@@ -276,6 +278,8 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=

View File

@@ -20,7 +20,7 @@
"PeersListenPort": 0,
"PreloadCache": 50,
"ReaderReadAHead": 95,
"RemoveCacheOnDrop": false,
"RemoveCacheOnDrop": true,
"ResponsiveMode": false,
"RetrackersMode": 1,
"SslCert": "",

53
server/torrserver.log Normal file
View File

@@ -0,0 +1,53 @@
2025/11/03 12:52:42 UTC0 XPathDBRouter: Registered new DB "JsonDB", total 1 DBs registered
2025/11/03 12:52:42 UTC0 XPathDBRouter: Registered new route "" for DB "JsonDB", total 1 routes
2025/11/03 12:52:42 UTC0 XPathDBRouter: Registered new route "settings" for DB "JsonDB", total 2 routes
2025/11/03 12:52:42 UTC0 XPathDBRouter: Registered new route "viewed" for DB "JsonDB", total 3 routes
2025/11/03 12:52:42 UTC0 XPathDBRouter: Registered new DB "TDB", total 2 DBs registered
2025/11/03 12:52:42 UTC0 XPathDBRouter: Registered new route "torrents" for DB "TDB", total 4 routes
2025/11/03 12:52:42 UTC0 Check web port 8090
2025/11/03 12:52:42 UTC0 Start TorrServer MatriX.136 torrent v1.2.22
2025/11/03 12:52:42 UTC0 Remove unused cache in dir: C:\Users\Erno\Videos\TorrServer\Cache
2025/11/03 12:52:42 UTC0 Local IPs: [172.30.80.1 192.168.1.197 fdd9:1fb5:5a0c:0:117c:986f:82f2:3f9b fdd9:1fb5:5a0c:0:e6fb:4f68:5267:b47e fdd9:1fb5:5a0c::265]
2025/11/03 12:52:42 UTC0 Set listen port to random autoselect (0)
2025/11/03 12:52:42 UTC0 Client config: {"CacheSize":134217728,"ReaderReadAHead":95,"PreloadCache":50,"UseDisk":true,"TorrentsSavePath":"C:\\Users\\Erno\\Videos\\TorrServer\\Cache","RemoveCacheOnDrop":true,"ForceEncrypt":false,"RetrackersMode":1,"TorrentDisconnectTimeout":30,"EnableDebug":false,"EnableDLNA":false,"FriendlyName":"Erno","EnableRutorSearch":false,"EnableIPv6":true,"DisableTCP":false,"DisableUTP":false,"DisableUPNP":true,"DisableDHT":false,"DisablePEX":false,"DisableUpload":false,"DownloadRateLimit":0,"UploadRateLimit":0,"ConnectionsLimit":25,"PeersListenPort":0,"SslPort":0,"SslCert":"","SslKey":"","ResponsiveMode":false,"JlfnAddr":"C:\\Users\\Erno\\Videos\\TorrServer","JlfnAutoCreate":true,"TMDBApiKey":"8fe788ebb9d0066b6461fa7993c2371b","TorrServerHost":"http://192.168.1.197:8090"}
2025/11/03 12:52:42 UTC0 PublicIp4: 217.19.214.184
2025/11/03 12:52:42 UTC0 error getting public ipv6 address: racing lookup:
Get "https://icanhazip.com": dial tcp6: lookup icanhazip.com: getaddrinfow: The requested name is valid, but no data of the requested type was found.
lookup myip.opendns.com on 192.168.1.1:53: dial udp6: lookup resolver1.opendns.com: getaddrinfow: The requested name is valid, but no data of the requested type was found.
2025/11/03 12:52:42 UTC0 Start http server at :8090
2025/11/03 12:55:49 UTC0 XPathDBRouter: Registered new DB "JsonDB", total 1 DBs registered
2025/11/03 12:55:49 UTC0 XPathDBRouter: Registered new route "" for DB "JsonDB", total 1 routes
2025/11/03 12:55:49 UTC0 XPathDBRouter: Registered new route "settings" for DB "JsonDB", total 2 routes
2025/11/03 12:55:49 UTC0 XPathDBRouter: Registered new route "viewed" for DB "JsonDB", total 3 routes
2025/11/03 12:55:49 UTC0 XPathDBRouter: Registered new DB "TDB", total 2 DBs registered
2025/11/03 12:55:49 UTC0 XPathDBRouter: Registered new route "torrents" for DB "TDB", total 4 routes
2025/11/03 12:55:49 UTC0 Check web port 8090
2025/11/03 12:55:49 UTC0 Start TorrServer MatriX.136 torrent v1.2.22
2025/11/03 12:55:49 UTC0 Remove unused cache in dir: C:\Users\Erno\Videos\TorrServer\Cache
2025/11/03 12:55:49 UTC0 Local IPs: [172.30.80.1 192.168.1.197 fdd9:1fb5:5a0c:0:117c:986f:82f2:3f9b fdd9:1fb5:5a0c:0:e6fb:4f68:5267:b47e fdd9:1fb5:5a0c::265]
2025/11/03 12:55:49 UTC0 Set listen port to random autoselect (0)
2025/11/03 12:55:49 UTC0 Client config: {"CacheSize":134217728,"ReaderReadAHead":95,"PreloadCache":50,"UseDisk":true,"TorrentsSavePath":"C:\\Users\\Erno\\Videos\\TorrServer\\Cache","RemoveCacheOnDrop":true,"ForceEncrypt":false,"RetrackersMode":1,"TorrentDisconnectTimeout":30,"EnableDebug":false,"EnableDLNA":false,"FriendlyName":"Erno","EnableRutorSearch":false,"EnableIPv6":true,"DisableTCP":false,"DisableUTP":false,"DisableUPNP":true,"DisableDHT":false,"DisablePEX":false,"DisableUpload":false,"DownloadRateLimit":0,"UploadRateLimit":0,"ConnectionsLimit":25,"PeersListenPort":0,"SslPort":0,"SslCert":"","SslKey":"","ResponsiveMode":false,"JlfnAddr":"C:\\Users\\Erno\\Videos\\TorrServer","JlfnAutoCreate":true,"TMDBApiKey":"8fe788ebb9d0066b6461fa7993c2371b","TorrServerHost":"http://192.168.1.197:8090"}
2025/11/03 12:55:49 UTC0 PublicIp4: 217.19.214.184
2025/11/03 12:55:50 UTC0 error getting public ipv6 address: racing lookup:
Get "https://icanhazip.com": dial tcp6: lookup icanhazip.com: getaddrinfow: The requested name is valid, but no data of the requested type was found.
lookup myip.opendns.com on 192.168.1.1:53: dial udp6: lookup resolver1.opendns.com: getaddrinfow: The requested name is valid, but no data of the requested type was found.
2025/11/03 12:55:50 UTC0 Start http server at :8090
2025/11/03 13:07:54 UTC0 timeout
2025/11/03 13:07:54 UTC0 Error open bboltDB: C:\Users\Erno\Projects\TorrServerJellyfin\server\config.db
2025/11/03 13:08:13 UTC0 XPathDBRouter: Registered new DB "JsonDB", total 1 DBs registered
2025/11/03 13:08:13 UTC0 XPathDBRouter: Registered new route "" for DB "JsonDB", total 1 routes
2025/11/03 13:08:13 UTC0 XPathDBRouter: Registered new route "settings" for DB "JsonDB", total 2 routes
2025/11/03 13:08:13 UTC0 XPathDBRouter: Registered new route "viewed" for DB "JsonDB", total 3 routes
2025/11/03 13:08:13 UTC0 XPathDBRouter: Registered new DB "TDB", total 2 DBs registered
2025/11/03 13:08:13 UTC0 XPathDBRouter: Registered new route "torrents" for DB "TDB", total 4 routes
2025/11/03 13:08:13 UTC0 Check web port 8090
2025/11/03 13:08:13 UTC0 Start TorrServer MatriX.136 torrent v1.2.22
2025/11/03 13:08:13 UTC0 Remove unused cache in dir: C:\Users\Erno\Videos\TorrServer\Cache
2025/11/03 13:08:13 UTC0 Local IPs: [172.30.80.1 192.168.1.197 fdd9:1fb5:5a0c:0:117c:986f:82f2:3f9b fdd9:1fb5:5a0c:0:e6fb:4f68:5267:b47e fdd9:1fb5:5a0c::265]
2025/11/03 13:08:13 UTC0 Set listen port to random autoselect (0)
2025/11/03 13:08:13 UTC0 Client config: {"CacheSize":134217728,"ReaderReadAHead":95,"PreloadCache":50,"UseDisk":true,"TorrentsSavePath":"C:\\Users\\Erno\\Videos\\TorrServer\\Cache","RemoveCacheOnDrop":true,"ForceEncrypt":false,"RetrackersMode":1,"TorrentDisconnectTimeout":30,"EnableDebug":false,"EnableDLNA":false,"FriendlyName":"Erno","EnableRutorSearch":false,"EnableIPv6":true,"DisableTCP":false,"DisableUTP":false,"DisableUPNP":true,"DisableDHT":false,"DisablePEX":false,"DisableUpload":false,"DownloadRateLimit":0,"UploadRateLimit":0,"ConnectionsLimit":25,"PeersListenPort":0,"SslPort":0,"SslCert":"","SslKey":"","ResponsiveMode":false,"JlfnAddr":"C:\\Users\\Erno\\Videos\\TorrServer","JlfnAutoCreate":true,"TMDBApiKey":"8fe788ebb9d0066b6461fa7993c2371b","TorrServerHost":"http://192.168.1.197:8090"}
2025/11/03 13:08:13 UTC0 PublicIp4: 217.19.214.184
2025/11/03 13:08:13 UTC0 error getting public ipv6 address: racing lookup:
lookup myip.opendns.com on 192.168.1.1:53: dial udp6: lookup resolver1.opendns.com: getaddrinfow: The requested name is valid, but no data of the requested type was found.
Get "https://icanhazip.com": dial tcp6: lookup icanhazip.com: getaddrinfow: The requested name is valid, but no data of the requested type was found.
2025/11/03 13:08:13 UTC0 Start http server at :8090

View File

@@ -1,9 +1,20 @@
{
"6e6d991ddab58fb441e2d6b40d51dd8442a73796": {
"1": {},
"2": {}
},
"d402b750de5b23dc0078830cd34e844351bc6317": {
"1": {}
},
"dfe4ae56636cb7109820a160c71abfe79c839c53": {
"3": {}
"10": {},
"3": {},
"4": {},
"5": {},
"6": {},
"7": {},
"8": {},
"9": {}
},
"eca37848971524d8799d67df369b74e77e73d727": {
"1": {},
@@ -11,5 +22,8 @@
},
"ef89d0a6b1c990f06c13431383cd1212c0ee841f": {
"1": {}
},
"f68fc9093d90090a4257b33ec532680061fabc77": {
"1": {}
}
}