rewrite cli component with urfave/cli

This commit is contained in:
onyx-and-iris 2025-05-06 23:43:47 +01:00
parent a0c1f15ef0
commit a13d3d7da3
3 changed files with 92 additions and 55 deletions

View File

@ -1,75 +1,105 @@
package main package main
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"strings" "strings"
"time" "time"
log "github.com/sirupsen/logrus"
"github.com/onyx-and-iris/q3rcon-proxy/pkg/udpproxy" "github.com/onyx-and-iris/q3rcon-proxy/pkg/udpproxy"
log "github.com/sirupsen/logrus"
"github.com/urfave/cli/v3"
) )
type proxyConfig struct {
proxyHost string
targetHost string
portsMapping []string
sessionTimeout int
}
func main() { func main() {
loglevel := os.Getenv("Q3RCON_LOGLEVEL") cmd := &cli.Command{
if loglevel == "" { Name: "q3rcon-proxy",
loglevel = "info" Usage: "A Quake 3 RCON proxy server",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "proxy-host",
Value: "0.0.0.0",
Usage: "Proxy host address",
Sources: cli.EnvVars("Q3RCON_PROXY_HOST"),
},
&cli.StringFlag{
Name: "target-host",
Value: "127.0.0.1",
Usage: "Target host address",
Sources: cli.EnvVars("Q3RCON_TARGET_HOST"),
},
&cli.StringFlag{
Name: "ports-mapping",
Usage: "Proxy and target ports (proxy:target)",
Sources: cli.EnvVars("Q3RCON_PORTS_MAPPING"),
Required: true,
},
&cli.IntFlag{
Name: "session-timeout",
Value: 20,
Usage: "Session timeout in minutes",
Sources: cli.EnvVars("Q3RCON_SESSION_TIMEOUT"),
},
&cli.StringFlag{
Name: "loglevel",
Value: "info",
Usage: "Log level (trace, debug, info, warn, error, fatal, panic)",
Sources: cli.EnvVars("Q3RCON_LOGLEVEL"),
},
},
Action: func(_ context.Context, cmd *cli.Command) error {
errChan := make(chan error)
for mapping := range strings.SplitSeq(cmd.String("ports-mapping"), ";") {
cfg := proxyConfig{
proxyHost: cmd.String("proxy-host"),
targetHost: cmd.String("target-host"),
portsMapping: strings.Split(mapping, ":"),
sessionTimeout: cmd.Int("session-timeout"),
} }
level, err := log.ParseLevel(loglevel)
go initProxy(cfg, errChan)
}
// We don't expect to receive any errors from the channels, but if we do, we log and return early.
for err := range errChan {
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Invalid log level: %s\n", loglevel) log.Errorf("Error: %v", err)
os.Exit(1) return err
} }
log.SetLevel(level) }
return nil
proxyHost := os.Getenv("Q3RCON_PROXY_HOST") },
if proxyHost == "" {
proxyHost = "0.0.0.0"
} }
targetHost := os.Getenv("Q3RCON_TARGET_HOST") if err := cmd.Run(context.Background(), os.Args); err != nil {
if targetHost == "" { log.Fatal(err)
targetHost = "127.0.0.1" }
} }
proxies := os.Getenv("Q3RCON_TARGET_PORTS") func initProxy(cfg proxyConfig, errChan chan error) {
if proxies == "" { proxyPort, targetPort := cfg.portsMapping[0], cfg.portsMapping[1]
log.Fatal("env Q3RCON_TARGET_PORTS required")
}
sessionTimeout, err := getEnvInt("Q3RCON_SESSION_TIMEOUT") hostAddr := fmt.Sprintf("%s:%s", cfg.proxyHost, proxyPort)
if err != nil { proxyAddr := fmt.Sprintf("%s:%s", cfg.targetHost, targetPort)
log.Fatalf("unable to parse Q3RCON_SESSION_TIMEOUT: %s", err.Error())
}
if sessionTimeout == 0 {
sessionTimeout = 20
}
for _, proxy := range strings.Split(proxies, ";") {
go start(proxyHost, targetHost, proxy, sessionTimeout)
}
<-make(chan struct{})
}
func start(proxyHost, targetHost, ports string, sessionTimeout int) {
proxyPort, targetPort := func() (string, string) {
x := strings.Split(ports, ":")
return x[0], x[1]
}()
hostAddr := fmt.Sprintf("%s:%s", proxyHost, proxyPort)
proxyAddr := fmt.Sprintf("%s:%s", targetHost, targetPort)
c, err := udpproxy.New( c, err := udpproxy.New(
hostAddr, proxyAddr, hostAddr, proxyAddr,
udpproxy.WithSessionTimeout(time.Duration(sessionTimeout)*time.Minute)) udpproxy.WithSessionTimeout(time.Duration(cfg.sessionTimeout)*time.Minute))
if err != nil { if err != nil {
log.Fatal(err) errChan <- fmt.Errorf("failed to create proxy: %w", err)
return
} }
log.Printf("q3rcon-proxy initialized: [proxy] (%s) [target] (%s)", hostAddr, proxyAddr) log.Printf("q3rcon-proxy initialized: [proxy] (%s) [target] (%s)", hostAddr, proxyAddr)
log.Fatal(c.ListenAndServe()) errChan <- c.ListenAndServe()
} }

5
go.mod
View File

@ -4,6 +4,9 @@ go 1.24.0
toolchain go1.24.1 toolchain go1.24.1
require github.com/sirupsen/logrus v1.9.3 require (
github.com/sirupsen/logrus v1.9.3
github.com/urfave/cli/v3 v3.3.2
)
require golang.org/x/sys v0.32.0 // indirect require golang.org/x/sys v0.32.0 // indirect

8
go.sum
View File

@ -6,11 +6,15 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/urfave/cli/v3 v3.3.2 h1:BYFVnhhZ8RqT38DxEYVFPPmGFTEf7tJwySTXsVRrS/o=
github.com/urfave/cli/v3 v3.3.2/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=