From 8ef412915ad4a321c9159727ac379dfd86047dd1 Mon Sep 17 00:00:00 2001 From: Moses Narrow <36607567+0pcom@users.noreply.github.com> Date: Thu, 2 Apr 2026 11:40:07 -0500 Subject: [PATCH 1/4] Add --keyfile flag to dmsg-discovery for systemd-friendly key management --- cmd/dmsg-discovery/commands/dmsg-discovery.go | 7 ++++ pkg/cmdutil/keyfile.go | 38 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 pkg/cmdutil/keyfile.go diff --git a/cmd/dmsg-discovery/commands/dmsg-discovery.go b/cmd/dmsg-discovery/commands/dmsg-discovery.go index c26023e5..3dd22e9a 100644 --- a/cmd/dmsg-discovery/commands/dmsg-discovery.go +++ b/cmd/dmsg-discovery/commands/dmsg-discovery.go @@ -45,6 +45,7 @@ var ( testEnvironment bool pk cipher.PubKey sk cipher.SecKey + keyFile string dmsgPort uint16 authPassphrase string officialServers string @@ -68,6 +69,7 @@ func init() { RootCmd.Flags().BoolVar(&enableLoadTesting, "enable-load-testing", false, "enable load testing") RootCmd.Flags().BoolVar(&testEnvironment, "test-environment", false, "distinguished between prod and test environment") RootCmd.Flags().Var(&sk, "sk", "dmsg secret key\n\r") + RootCmd.Flags().StringVar(&keyFile, "keyfile", "", "path to file containing secret key (auto-generated if missing)\n\r") RootCmd.Flags().Uint16Var(&dmsgPort, "dmsgPort", dmsg.DefaultDmsgHTTPPort, "dmsg port value\n\r") RootCmd.Flags().StringVar(&dmsgServerType, "dmsg-server-type", "", "type of dmsg server on dmsghttp handler") } @@ -115,6 +117,11 @@ Example: log := sf.Logger() var err error + if keyFile != "" { + if err = dmsgcmdutil.LoadOrGenerateKey(keyFile, &sk); err != nil { + log.Fatal("Failed to load keyfile: ", err) + } + } if pk, err = sk.PubKey(); err != nil { log.WithError(err).Warn("No SecKey found. Skipping serving on dmsghttp.") } diff --git a/pkg/cmdutil/keyfile.go b/pkg/cmdutil/keyfile.go new file mode 100644 index 00000000..9c838844 --- /dev/null +++ b/pkg/cmdutil/keyfile.go @@ -0,0 +1,38 @@ +// Package cmdutil pkg/cmdutil/keyfile.go +package cmdutil + +import ( + "fmt" + "os" + "strings" + + coinCipher "github.com/skycoin/skycoin/src/cipher" + + "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cipher" +) + +// LoadOrGenerateKey loads a secret key from a file, or generates a new keypair +// and writes it to the file if it doesn't exist. This allows systemd services +// to auto-generate their identity on first run without bash workarounds. +func LoadOrGenerateKey(path string, sk *cipher.SecKey) error { + data, err := os.ReadFile(path) //nolint:gosec + if err == nil { + trimmed := strings.TrimSpace(string(data)) + if trimmed == "" { + return fmt.Errorf("keyfile %s is empty", path) + } + return sk.Set(trimmed) + } + if !os.IsNotExist(err) { + return fmt.Errorf("failed to read keyfile %s: %w", path, err) + } + // Auto-generate new keypair + pk, newSK := coinCipher.GenerateKeyPair() + content := newSK.Hex() + "\n" + if err := os.WriteFile(path, []byte(content), 0600); err != nil { + return fmt.Errorf("failed to write keyfile %s: %w", path, err) + } + *sk = cipher.SecKey(newSK) + fmt.Printf("Generated new keypair in %s\nPublic key: %s\n", path, pk.Hex()) + return nil +} From ff0d4fbf329c2f64013889ad00552fbd20b2eccf Mon Sep 17 00:00:00 2001 From: Moses Narrow <36607567+0pcom@users.noreply.github.com> Date: Thu, 2 Apr 2026 12:05:04 -0500 Subject: [PATCH 2/4] Replace duplicated scriptExec* with shared cmdutil.Skyenv* library - Vendor skywire 90c6d9b with shared Skyenv* helpers in cmdutil - Remove ~220 lines of duplicated scriptExec* functions from dmsgweb - dmsgweb.go and dmsgwebsrv.go now use cmdutil.SkyenvString, cmdutil.SkyenvUint, cmdutil.SkyenvStringSlice, etc. - Fixes Windows array bug (inverted len check) and PowerShell quoting --- cmd/dmsgweb/commands/dmsgweb.go | 14 +- cmd/dmsgweb/commands/dmsgwebsrv.go | 12 +- cmd/dmsgweb/commands/root.go | 223 --------------- go.mod | 5 +- go.sum | 8 +- .../skycoin/skywire/pkg/skyenv/skyenv.go | 258 ++++++++++++++++++ .../skywire/pkg/skyenv/skyenv_darwin.go | 27 ++ .../skywire/pkg/skyenv/skyenv_linux.go | 27 ++ .../skywire/pkg/skyenv/skyenv_windows.go | 27 ++ .../pkg/buildinfo/buildinfo.go | 3 +- .../skywire-utilities/pkg/cmdutil/keyfile.go | 38 +++ .../pkg/cmdutil/service_flags.go | 3 +- .../skywire-utilities/pkg/cmdutil/skyenv.go | 179 ++++++++++++ .../skywire-utilities/pkg/httputil/health.go | 8 +- .../skywire-utilities/pkg/metricsutil/http.go | 46 ++++ vendor/modules.txt | 9 +- 16 files changed, 633 insertions(+), 254 deletions(-) create mode 100644 vendor/github.com/skycoin/skywire/pkg/skyenv/skyenv.go create mode 100644 vendor/github.com/skycoin/skywire/pkg/skyenv/skyenv_darwin.go create mode 100644 vendor/github.com/skycoin/skywire/pkg/skyenv/skyenv_linux.go create mode 100644 vendor/github.com/skycoin/skywire/pkg/skyenv/skyenv_windows.go create mode 100644 vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cmdutil/keyfile.go create mode 100644 vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cmdutil/skyenv.go diff --git a/cmd/dmsgweb/commands/dmsgweb.go b/cmd/dmsgweb/commands/dmsgweb.go index a1dcb6c1..87554d7a 100644 --- a/cmd/dmsgweb/commands/dmsgweb.go +++ b/cmd/dmsgweb/commands/dmsgweb.go @@ -51,16 +51,16 @@ const dwenv = "DMSGWEB" var dwcfg = os.Getenv(dwenv) func init() { - webPort = scriptExecUintSlice("${WEBPORT[@]:-8080}", dwcfg) - proxyPort = scriptExecUint("${PROXYPORT:-4445}", dwcfg) - addProxy = scriptExecString("${ADDPROXY}", dwcfg) - resolveDmsgAddr = scriptExecStringSlice("${RESOLVEPK[@]}", dwcfg) - rawTCP = scriptExecBoolSlice("${RAWTCP[@]:-false}", dwcfg) + webPort = cmdutil.SkyenvUintSlice("${WEBPORT[@]:-8080}", dwcfg) + proxyPort = cmdutil.SkyenvUint("${PROXYPORT:-4445}", dwcfg) + addProxy = cmdutil.SkyenvString("${ADDPROXY}", dwcfg) + resolveDmsgAddr = cmdutil.SkyenvStringSlice("${RESOLVEPK[@]}", dwcfg) + rawTCP = cmdutil.SkyenvBoolSlice("${RAWTCP[@]:-false}", dwcfg) if os.Getenv("DMSGWEBSK") != "" { sk.Set(os.Getenv("DMSGWEBSK")) //nolint } - if scriptExecString("${DMSGWEBSK}", dwcfg) != "" { - sk.Set(scriptExecString("${DMSGWEBSK}", dwcfg)) //nolint + if cmdutil.SkyenvString("${DMSGWEBSK}", dwcfg) != "" { + sk.Set(cmdutil.SkyenvString("${DMSGWEBSK}", dwcfg)) //nolint } pk, _ = sk.PubKey() //nolint diff --git a/cmd/dmsgweb/commands/dmsgwebsrv.go b/cmd/dmsgweb/commands/dmsgwebsrv.go index 3622c1c6..cb5f40ca 100644 --- a/cmd/dmsgweb/commands/dmsgwebsrv.go +++ b/cmd/dmsgweb/commands/dmsgwebsrv.go @@ -29,15 +29,15 @@ const dwsenv = "DMSGWEBSRV" var dwscfg = os.Getenv(dwsenv) func init() { - dmsgPort = scriptExecUintSlice("${DMSGPORT[@]:-80}", dwscfg) - wl = scriptExecStringSlice("${WHITELISTPKS[@]}", dwscfg) - localPort = scriptExecUintSlice("${LOCALPORT[@]:-8086}", dwscfg) - rawTCP = scriptExecBoolSlice("${RAWTCP[@]:-false}", dwscfg) + dmsgPort = cmdutil.SkyenvUintSlice("${DMSGPORT[@]:-80}", dwscfg) + wl = cmdutil.SkyenvStringSlice("${WHITELISTPKS[@]}", dwscfg) + localPort = cmdutil.SkyenvUintSlice("${LOCALPORT[@]:-8086}", dwscfg) + rawTCP = cmdutil.SkyenvBoolSlice("${RAWTCP[@]:-false}", dwscfg) if os.Getenv("DMSGWEBSRVSK") != "" { sk.Set(os.Getenv("DMSGWEBSRVSK")) //nolint } - if scriptExecString("${DMSGWEBSRVSK}", dwscfg) != "" { - sk.Set(scriptExecString("${DMSGWEBSRVSK}", dwscfg)) //nolint + if cmdutil.SkyenvString("${DMSGWEBSRVSK}", dwscfg) != "" { + sk.Set(cmdutil.SkyenvString("${DMSGWEBSRVSK}", dwscfg)) //nolint } pk, _ = sk.PubKey() //nolint diff --git a/cmd/dmsgweb/commands/root.go b/cmd/dmsgweb/commands/root.go index b036c2c8..daf666b5 100644 --- a/cmd/dmsgweb/commands/root.go +++ b/cmd/dmsgweb/commands/root.go @@ -3,18 +3,15 @@ package commands import ( "fmt" - "log" "net" "net/http" "os" "runtime" - "strconv" "strings" "sync" "time" "github.com/bitfield/script" - "github.com/chen3feng/safecast" "github.com/gin-gonic/gin" "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cipher" "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/logging" @@ -76,226 +73,6 @@ func printEnvs(envfile string) { os.Exit(0) } -//TODO: these functions are more or less duplicated in several places - need to standardize and put in it's own library import in "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/..." - -func scriptExecString(s, envfile string) string { - if runtime.GOOS == "windows" { - var variable, defaultvalue string - if strings.Contains(s, ":-") { - parts := strings.SplitN(s, ":-", 2) - variable = parts[0] + "}" - defaultvalue = strings.TrimRight(parts[1], "}") - } else { - variable = s - defaultvalue = "" - } - out, err := script.Exec(fmt.Sprintf(`powershell -c '$SKYENV = "%s"; if ($SKYENV -ne "" -and (Test-Path $SKYENV)) { . $SKYENV }; echo %s"`, envfile, variable)).String() - if err == nil { - if (out == "") || (out == variable) { - return defaultvalue - } - return strings.TrimRight(out, "\n") - } - return defaultvalue - } - z, err := script.Exec(fmt.Sprintf(`bash -c 'SKYENV=%s ; if [[ $SKYENV != "" ]] && [[ -f $SKYENV ]] ; then source $SKYENV ; fi ; printf "%s"'`, envfile, s)).String() - if err == nil { - return strings.TrimSpace(z) - } - return "" -} - -/* - func scriptExecArray(s, envfile string) string { - if runtime.GOOS == "windows" { - variable := s - if strings.Contains(variable, "[@]}") { - variable = strings.TrimRight(variable, "[@]}") - variable = strings.TrimRight(variable, "{") - } - out, err := script.Exec(fmt.Sprintf(`powershell -c '$SKYENV = "%s"; if ($SKYENV -ne "" -and (Test-Path $SKYENV)) { . $SKYENV }; foreach ($item in %s) { Write-Host $item }'`, envfile, variable)).Slice() - if err == nil { - if len(out) != 0 { - return "" - } - return strings.Join(out, ",") - } - } - y, err := script.Exec(fmt.Sprintf(`bash -c 'SKYENV=%s ; if [[ $SKYENV != "" ]] && [[ -f $SKYENV ]] ; then source $SKYENV ; fi ; for _i in %s ; do echo "$_i" ; done'`, envfile, s)).Slice() - if err == nil { - return strings.Join(y, ",") - } - return "" - } -*/ - -func scriptExecStringSlice(s, envfile string) []string { - if runtime.GOOS == "windows" { - variable := s - if strings.Contains(variable, "[@]}") { - variable = strings.TrimRight(variable, "[@]}") - variable = strings.TrimRight(variable, "{") - } - out, err := script.Exec(fmt.Sprintf(`powershell -c '$SKYENV = "%s"; if ($SKYENV -ne "" -and (Test-Path $SKYENV)) { . $SKYENV }; foreach ($item in %s) { Write-Host $item }'`, envfile, variable)).Slice() - if err == nil { - return out - } - } - y, err := script.Exec(fmt.Sprintf(`bash -c 'SKYENV=%s ; if [[ $SKYENV != "" ]] && [[ -f $SKYENV ]] ; then source $SKYENV ; fi ; for _i in %s ; do echo "$_i" ; done'`, envfile, s)).Slice() - if err == nil { - return y - } - return []string{} -} - -func scriptExecBoolSlice(s, envfile string) []bool { - var result []bool - - if runtime.GOOS == "windows" { - variable := s - if strings.Contains(variable, "[@]}") { - variable = strings.TrimRight(variable, "[@]}") - variable = strings.TrimRight(variable, "{") - } - out, err := script.Exec(fmt.Sprintf(`powershell -c '$SKYENV = "%s"; if ($SKYENV -ne "" -and (Test-Path $SKYENV)) { . $SKYENV }; foreach ($item in %s) { Write-Host $item }'`, envfile, variable)).Slice() - if err == nil { - for _, item := range out { - result = append(result, item != "") - } - return result - } - } else { - y, err := script.Exec(fmt.Sprintf(`bash -c 'SKYENV=%s ; if [[ $SKYENV != "" ]] && [[ -f $SKYENV ]] ; then source $SKYENV ; fi ; for _i in %s ; do echo "$_i" ; done'`, envfile, s)).Slice() - if err == nil { - for _, item := range y { - switch strings.ToLower(item) { - case "true": - result = append(result, true) - case "false": - result = append(result, false) - default: - result = append(result, false) - } - } - return result - } - } - if len(result) == 0 { - result = append(result, false) - } - return result -} - -func scriptExecUintSlice(s, envfile string) []uint { - var out []string - var err error - - if runtime.GOOS == "windows" { - variable := s - if strings.Contains(variable, "[@]}") { - variable = strings.TrimRight(variable, "[@]}") - variable = strings.TrimRight(variable, "{") - } - out, err = script.Exec(fmt.Sprintf(`powershell -c '$SKYENV = "%s"; if ($SKYENV -ne "" -and (Test-Path $SKYENV)) { . $SKYENV }; foreach ($item in %s) { Write-Host $item }'`, envfile, variable)).Slice() - } else { - out, err = script.Exec(fmt.Sprintf(`bash -c 'SKYENV=%s ; if [[ $SKYENV != "" ]] && [[ -f $SKYENV ]] ; then source $SKYENV ; fi ; for _i in %s ; do echo "$_i" ; done'`, envfile, s)).Slice() - } - - if err != nil { - return []uint{} - } - - var res []uint - for _, item := range out { - num, err := strconv.ParseUint(item, 10, 64) - if err == nil { - res = append(res, uint(num)) - } - } - - return res -} - -/* -func scriptExecInt(s, envfile string) int { - if runtime.GOOS == "windows" { - var variable string - if strings.Contains(s, ":-") { - parts := strings.SplitN(s, ":-", 2) - variable = parts[0] + "}" - } else { - variable = s - } - out, err := script.Exec(fmt.Sprintf(`powershell -c '$SKYENV = "%s"; if ($SKYENV -ne "" -and (Test-Path $SKYENV)) { . $SKYENV }; echo %s"`, envfile, variable)).String() - if err == nil { - if (out == "") || (out == variable) { - return 0 - } - i, err := strconv.Atoi(strings.TrimSpace(strings.TrimRight(out, "\n"))) - if err == nil { - return i - } - return 0 - } - return 0 - } - z, err := script.Exec(fmt.Sprintf(`bash -c 'SKYENV=%s ; if [[ $SKYENV != "" ]] && [[ -f $SKYENV ]] ; then source $SKYENV ; fi ; printf "%s"'`, envfile, s)).String() - if err == nil { - if z == "" { - return 0 - } - i, err := strconv.Atoi(z) - if err == nil { - return i - } - } - return 0 -} -*/ - -func scriptExecUint(s, envfile string) uint { - if runtime.GOOS == "windows" { - var variable string - if strings.Contains(s, ":-") { - parts := strings.SplitN(s, ":-", 2) - variable = parts[0] + "}" - } else { - variable = s - } - out, err := script.Exec(fmt.Sprintf(`powershell -c '$SKYENV = "%s"; if ($SKYENV -ne "" -and (Test-Path $SKYENV)) { . $SKYENV }; echo %s"`, envfile, variable)).String() - if err == nil { - if (out == "") || (out == variable) { - return 0 - } - i, err := strconv.Atoi(strings.TrimSpace(strings.TrimRight(out, "\n"))) - if err == nil { - u, ok := safecast.To[uint](i) - if !ok { - log.Fatal("uint overflow") - } - return u - } - return 0 - } - return 0 - } - z, err := script.Exec(fmt.Sprintf(`bash -c 'SKYENV=%s ; if [[ $SKYENV != "" ]] && [[ -f $SKYENV ]] ; then source $SKYENV ; fi ; printf "%s"'`, envfile, s)).String() - if err == nil { - if z == "" { - return 0 - } - i, err := strconv.Atoi(z) - if err == nil { - u, ok := safecast.To[uint](i) - if !ok { - log.Fatal("uint overflow") - } - return u - } - } - return uint(0) -} - func whitelistAuth(whitelistedPKs []cipher.PubKey) gin.HandlerFunc { return func(c *gin.Context) { remotePK, _, err := net.SplitHostPort(c.Request.RemoteAddr) diff --git a/go.mod b/go.mod index a81da9a6..aa8d21de 100644 --- a/go.mod +++ b/go.mod @@ -19,8 +19,8 @@ require ( github.com/pires/go-proxyproto v0.11.0 github.com/sirupsen/logrus v1.9.4 github.com/skycoin/noise v0.0.0-20180327030543-2492fe189ae6 - github.com/skycoin/skycoin v0.28.6-0.20260328152706-a360adb0a4d3 - github.com/skycoin/skywire v1.3.40-0.20260328171146-a5facdc74e72 + github.com/skycoin/skycoin v0.28.6-0.20260401142608-a27afbb0b33b + github.com/skycoin/skywire v1.3.40-0.20260402164617-90c6d9b52e01 github.com/spf13/cobra v1.10.2 github.com/stretchr/testify v1.11.1 golang.org/x/net v0.52.0 @@ -99,7 +99,6 @@ require ( golang.org/x/arch v0.25.0 // indirect golang.org/x/crypto v0.49.0 // indirect golang.org/x/text v0.35.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.2 // indirect diff --git a/go.sum b/go.sum index b6c7ad95..aa6efeca 100644 --- a/go.sum +++ b/go.sum @@ -170,10 +170,10 @@ github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/skycoin/noise v0.0.0-20180327030543-2492fe189ae6 h1:1Nc5EBY6pjfw1kwW0duwyG+7WliWz5u9kgk1h5MnLuA= github.com/skycoin/noise v0.0.0-20180327030543-2492fe189ae6/go.mod h1:UXghlricA7J3aRD/k7p/zBObQfmBawwCxIVPVjz2Q3o= -github.com/skycoin/skycoin v0.28.6-0.20260328152706-a360adb0a4d3 h1:yDTe5ISvCGJ/wsbaXcXKO/MgyB8/pam3GHsvwbVlPk8= -github.com/skycoin/skycoin v0.28.6-0.20260328152706-a360adb0a4d3/go.mod h1:tgVxjBBV4/OxVBDrcpsVK0q/awGxqBjwTUPDBMh9ZcA= -github.com/skycoin/skywire v1.3.40-0.20260328171146-a5facdc74e72 h1:KXG0RGXVDh2KNn35kGC5+mWXU6hlYYjowU9TXiGSwMU= -github.com/skycoin/skywire v1.3.40-0.20260328171146-a5facdc74e72/go.mod h1:GmBT7f+kIxR2ym3iBbtBNE75/W8XfG4JbkJODzD2azA= +github.com/skycoin/skycoin v0.28.6-0.20260401142608-a27afbb0b33b h1:APVwC2cIsVDkwKWkpLLcqu5gcfiMDPyGKcSY5/HIbSk= +github.com/skycoin/skycoin v0.28.6-0.20260401142608-a27afbb0b33b/go.mod h1:tgVxjBBV4/OxVBDrcpsVK0q/awGxqBjwTUPDBMh9ZcA= +github.com/skycoin/skywire v1.3.40-0.20260402164617-90c6d9b52e01 h1:CwTGPewYPlm18Tmdeyl3+LZWsNMvsSoPGcSrUIXdhi0= +github.com/skycoin/skywire v1.3.40-0.20260402164617-90c6d9b52e01/go.mod h1:jXotLTnJTUYol7ht75H2ik9zmfezUiYb+XdMAeoaMcQ= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= diff --git a/vendor/github.com/skycoin/skywire/pkg/skyenv/skyenv.go b/vendor/github.com/skycoin/skywire/pkg/skyenv/skyenv.go new file mode 100644 index 00000000..867a9103 --- /dev/null +++ b/vendor/github.com/skycoin/skywire/pkg/skyenv/skyenv.go @@ -0,0 +1,258 @@ +// Package skyenv defines variables and constants +package skyenv + +import ( + "time" +) + +const ( + // config file constants + + // ConfigName is the default config name. Updated by setting config file path. + ConfigName = "skywire-config.json" + // DMSGHTTPName is the default dmsghttp config name + DMSGHTTPName = "dmsghttp-config.json" + // SERVICESName is the default services config name - should be the same contents as conf.skywire.skycoin.com or hardcoded fallback in skywire-utilities/pkg/skyenv + SERVICESName = "services-config.json" + + // Dmsg port constants. + + // DmsgCtrlPort Listening port for dmsgctrl protocol (similar to TCP Echo Protocol). + DmsgCtrlPort uint16 = 7 + + // DmsgPingPort Listening port for dmsg ping protocol. + DmsgPingPort uint16 = 8 + + // DmsgSetupPort Listening port of a setup node. + DmsgSetupPort uint16 = 36 + + // DmsgHypervisorPort Listening port of a hypervisor for incoming RPC visor connections over dmsg. + DmsgHypervisorPort uint16 = 46 + + // DmsgTransportSetupPort Listening port for transport setup RPC over dmsg. + DmsgTransportSetupPort uint16 = 47 + + // DmsgTransportSetupServicePort Listening port for transport setup service requests over dmsg. + // This is the port where TPS nodes listen for incoming transport setup requests from visors. + DmsgTransportSetupServicePort uint16 = 48 + + // DmsgGRPCPort is the DMSG port for gRPC services (remote gotop, stats, etc.) + DmsgGRPCPort uint16 = 49 + + // DmsgAwaitSetupPort Listening port of a visor for setup operations. + DmsgAwaitSetupPort uint16 = 136 + + // Transport port constants. + + // TransportPort Listening port of a visor for incoming transports. + TransportPort uint16 = 45 + + // LatencyProbePort is the Skywire routing port for transport latency probes. + // Note: same number as DmsgHypervisorPort but different namespace (routing vs DMSG). + LatencyProbePort uint16 = 46 + + // PublicAutoconnect determines if the visor automatically creates stcpr transports to public visors + PublicAutoconnect = true + + // Dmsgpty constants. + + // DmsgPtyPort is the dmsg port to listen on for dmsgpty connections + DmsgPtyPort uint16 = 22 + + // DmsgPtyCLINet is the type of cli net used by dmsgpty + DmsgPtyCLINet = "unix" + + // Skywire-TCP constants. + + // STCPAddr is the address to listen for stcpr or stcp transports + STCPAddr = ":7777" + + // Default skywire app constants. + + // SkychatName is the name of the skychat app + SkychatName = "skychat" + + // SkychatPort is the dmsg port used by skychat + SkychatPort uint16 = 1 + + // SkychatAddr is the non-dmsg port used to access the skychat app on localhost + SkychatAddr = ":8001" + + // SkysocksName is the name of the skysocks app + SkysocksName = "skysocks" + + // SkysocksPort is the skysocks port on dmsg + SkysocksPort uint16 = 3 + + // SkysocksClientName is the skysocks-client app name + SkysocksClientName = "skysocks-client" + + // SkysocksClientPort is the skysocks-client app dmsg port + SkysocksClientPort uint16 = 13 + + // SkysocksClientAddr is the default port the socks5 proxy client serves on + SkysocksClientAddr = ":1080" + + // VPNServerName is the name of the vpn server app + VPNServerName = "vpn-server" + + // VPNServerPort is the vpn server dmsg port + VPNServerPort uint16 = 44 + + // VPNClientName is the name of the vpn client app + VPNClientName = "vpn-client" + + // VPNClientPort over dmsg + VPNClientPort uint16 = 43 + + // ExampleServerName is the name of the example server app + ExampleServerName = "example-server-app" + + // ExampleServerPort is dmsg port of example server app + // Previously 45 — conflicted with TransportPort + ExampleServerPort uint16 = 55 + + // SkyForwardingServerName name of sky forwarding server app (built-in) + SkyForwardingServerName = "sky-forwarding" + + // SkyForwardingServerPort skynet port of skyfwd server app (built-in) + // Previously 47 — conflicted with DmsgTransportSetupPort + SkyForwardingServerPort uint16 = 57 + + // SkyPingPort dmsg port of sky ping + // Previously 48 — conflicted with DmsgTransportSetupServicePort + SkyPingPort uint16 = 58 + + // RPC constants. + + // RPCAddr for skywire-cli to access skywire-visor + RPCAddr = "localhost:3435" + + // RPCTimeout timeout of rpc requests + RPCTimeout = 20 * time.Second + + // TransportRPCTimeout timeout of transport rpc + TransportRPCTimeout = 1 * time.Minute + + // UpdateRPCTimeout is the RPC timeout for the "Update" method (used by rpcClient.Call) + UpdateRPCTimeout = 6 * time.Hour + + // HealthTimeout defines timeout for /health endpoint calls done from hypervisor. + HealthTimeout = 5 * time.Second + + // InnerHealthTimeout defines timeout for /health endpoint calls done from visor. + // Kept less than HealthTimeout so that the outer call completes. + InnerHealthTimeout = 3 * time.Second + + // DMSG tracker constants. + + // DmsgTrackerUpdateInterval is the interval for updating DMSG client summaries. + DmsgTrackerUpdateInterval = 30 * time.Second + + // DmsgTrackerUpdateTimeout is the timeout for a single DMSG tracker update. + DmsgTrackerUpdateTimeout = 10 * time.Second + + // Visor registration constants. + + // PublicVisorRegistrationTimeout is the timeout for registering as a public visor. + PublicVisorRegistrationTimeout = 10 * time.Minute + + // HTTP server timeout constants (for dmsg and local HTTP servers). + + // HTTPReadTimeout is the maximum duration for reading the entire request. + HTTPReadTimeout = 30 * time.Second + + // HTTPWriteTimeout is the maximum duration before timing out writes of the response. + HTTPWriteTimeout = 60 * time.Second + + // HTTPIdleTimeout is the maximum time to wait for the next request when keep-alives are enabled. + HTTPIdleTimeout = 90 * time.Second + + // HTTPReadHeaderTimeout is the amount of time allowed to read request headers. + HTTPReadHeaderTimeout = 10 * time.Second + + // Default skywire app server and discovery constants + + // AppSrvAddr address of app server + AppSrvAddr = "localhost:5505" + + // ServiceDiscUpdateInterval update interval (heartbeat) for apps in service discovery + ServiceDiscUpdateInterval = 90 * time.Second + + // PublicAutoconnectInterval interval for checking service discovery and connecting to public visors + PublicAutoconnectInterval = 300 * time.Second + + // AppBinPath is the default path for the apps + AppBinPath = "./" + + // LogLevel is the default log level of the visor + LogLevel = "info" + + // Routing constants + + // TpLogStore is where tp logs are stored + TpLogStore = "transport_logs" + + // LatencyLogStore is where transport latency logs are stored + LatencyLogStore = "latency_logs" + + // Custom path to serve files from dmsghttp log server over dmsg + Custom = "custom" + + // LocalPath where the visor writes files to + LocalPath = "./local" + + // Default hypervisor constants + + // EnableAuth enables auth on the hypervisor UI + EnableAuth = false + + // EnableTLS enables tls for accessing hypervisor ui + EnableTLS = false + + // TLSKey for access to hvui + TLSKey = "./ssl/key.pem" + + // TLSCert for access to hvui + TLSCert = "./ssl/cert.pem" + + // IPCShutdownMessageType sends IPC shutdown message type + IPCShutdownMessageType = 68 + + // IsPublic advertises the visor in the service discovery + IsPublic = false + + // RewardFile is the name of the file containing skycoin rewards address and privacy setting + RewardFile string = "reward.txt" + + // NodeInfo is the name of the survey file + NodeInfo string = "node-info.json" + + // GeoIP is the URL of default geoip service to work with IP + GeoIP string = "http://ip.skycoin.com" +) + +// SkywireConfig returns the full path to the package config +func SkywireConfig() string { + return SkywirePath + "/" + ConfigJSON +} + +// PkgConfig struct contains paths specific to the installation +type PkgConfig struct { + LauncherBinPath string `json:"launcher"` + LocalPath string `json:"local_path"` + Hypervisor `json:"hypervisor"` + // TLSCertFile string `json:"tls_cert_file"` + // TLSKeyFile string `json:"tls_key_file"` +} + +// LauncherBinPath struct contains the BinPath specific to the installation +type LauncherBinPath struct { + BinPath string `json:"bin_path"` +} + +// Hypervisor struct contains Hypervisor paths specific to the installation +type Hypervisor struct { + DbPath string `json:"db_path"` + EnableAuth bool `json:"enable_auth"` +} diff --git a/vendor/github.com/skycoin/skywire/pkg/skyenv/skyenv_darwin.go b/vendor/github.com/skycoin/skywire/pkg/skyenv/skyenv_darwin.go new file mode 100644 index 00000000..47723a1a --- /dev/null +++ b/vendor/github.com/skycoin/skywire/pkg/skyenv/skyenv_darwin.go @@ -0,0 +1,27 @@ +//go:build darwin +// +build darwin + +// Package skyenv defines variables and constants +package skyenv + +const ( + //OS detection at runtime + OS = "mac" + // SkywirePath is the path to the installation folder. + SkywirePath = "/Library/Application Support/Skywire" + // ConfigJSON is the config name generated by the script included with the installation on mac + ConfigJSON = "skywire-config.json" +) + +// PackageConfig contains installation paths (for mac) +func PackageConfig() PkgConfig { + pkgConfig := PkgConfig{ + LauncherBinPath: "/Applications/Skywire.app/Contents/MacOS", //apps are now subcommands of the skywire binary "/Applications/Skywire.app/Contents/MacOS/apps", + LocalPath: "/Library/Application Support/Skywire/local", + Hypervisor: Hypervisor{ + DbPath: "/Library/Application Support/Skywire/users.db", + EnableAuth: true, + }, + } + return pkgConfig +} diff --git a/vendor/github.com/skycoin/skywire/pkg/skyenv/skyenv_linux.go b/vendor/github.com/skycoin/skywire/pkg/skyenv/skyenv_linux.go new file mode 100644 index 00000000..70477f9e --- /dev/null +++ b/vendor/github.com/skycoin/skywire/pkg/skyenv/skyenv_linux.go @@ -0,0 +1,27 @@ +//go:build linux +// +build linux + +// Package skyenv defines variables and constants +package skyenv + +const ( + //OS detection at runtime + OS = "linux" + // SkywirePath is the path to the installation folder for the linux packages. + SkywirePath = "/opt/skywire" + // ConfigJSON is the config name generated by the skywire-autocofig script in the linux packages + ConfigJSON = "skywire.json" +) + +// PackageConfig contains installation paths (for linux) +func PackageConfig() PkgConfig { + pkgConfig := PkgConfig{ + LauncherBinPath: "/opt/skywire/bin", //apps are now subcommands of the skywire binary "/opt/skywire/apps", + LocalPath: "/opt/skywire/local", + Hypervisor: Hypervisor{ + DbPath: "/opt/skywire/users.db", + EnableAuth: true, + }, + } + return pkgConfig +} diff --git a/vendor/github.com/skycoin/skywire/pkg/skyenv/skyenv_windows.go b/vendor/github.com/skycoin/skywire/pkg/skyenv/skyenv_windows.go new file mode 100644 index 00000000..ebf339f5 --- /dev/null +++ b/vendor/github.com/skycoin/skywire/pkg/skyenv/skyenv_windows.go @@ -0,0 +1,27 @@ +//go:build windows +// +build windows + +// Package skyenv defines variables and constants +package skyenv + +const ( + //OS detection at runtime + OS = "win" + // SkywirePath is the path to the installation folder for the .msi + SkywirePath = "C:/Program Files/Skywire" + // ConfigJSON is the config name generated by the batch file included with the windows .msi + ConfigJSON = "skywire-config.json" +) + +// PackageConfig contains installation paths (for windows) +func PackageConfig() PkgConfig { + pkgConfig := PkgConfig{ + LauncherBinPath: "C:/Program Files/Skywire", + LocalPath: "C:/Program Files/Skywire/local", + Hypervisor: Hypervisor{ + DbPath: "C:/Program Files/Skywire/users.db", + EnableAuth: true, + }, + } + return pkgConfig +} diff --git a/vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/buildinfo/buildinfo.go b/vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/buildinfo/buildinfo.go index 46d4711e..1069e91d 100644 --- a/vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/buildinfo/buildinfo.go +++ b/vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/buildinfo/buildinfo.go @@ -25,9 +25,8 @@ var ( // format hint: bi.Main.Version = v1.3.29-rc7.0.20250410212328-dc5d22b7ab2a var bi *debug.BuildInfo -// TODO: deprecate? -// $ go build -ldflags="-X 'github.com/skycoin/skywire/pkg/skywire-utilities/pkg/buildinfo.golist=$(go list -m -json -mod=mod github.com/skycoin/@)' -X 'github.com/skycoin/skywire/pkg/skywire-utilities/pkg/buildinfo.date=$(date -u "+%Y-%m-%dT%H:%M:%SZ")'" . // ldflags-provided module info (`go list -m -json`) +// $ go build -ldflags="-X 'github.com/skycoin/skywire/pkg/skywire-utilities/pkg/buildinfo.golist=$(go list -m -json -mod=mod github.com/skycoin/@)' -X 'github.com/skycoin/skywire/pkg/skywire-utilities/pkg/buildinfo.date=$(date -u "+%Y-%m-%dT%H:%M:%SZ")'" . var golist string // ModuleInfo represents the JSON structure returned by `go list -m -json`. diff --git a/vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cmdutil/keyfile.go b/vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cmdutil/keyfile.go new file mode 100644 index 00000000..9c838844 --- /dev/null +++ b/vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cmdutil/keyfile.go @@ -0,0 +1,38 @@ +// Package cmdutil pkg/cmdutil/keyfile.go +package cmdutil + +import ( + "fmt" + "os" + "strings" + + coinCipher "github.com/skycoin/skycoin/src/cipher" + + "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cipher" +) + +// LoadOrGenerateKey loads a secret key from a file, or generates a new keypair +// and writes it to the file if it doesn't exist. This allows systemd services +// to auto-generate their identity on first run without bash workarounds. +func LoadOrGenerateKey(path string, sk *cipher.SecKey) error { + data, err := os.ReadFile(path) //nolint:gosec + if err == nil { + trimmed := strings.TrimSpace(string(data)) + if trimmed == "" { + return fmt.Errorf("keyfile %s is empty", path) + } + return sk.Set(trimmed) + } + if !os.IsNotExist(err) { + return fmt.Errorf("failed to read keyfile %s: %w", path, err) + } + // Auto-generate new keypair + pk, newSK := coinCipher.GenerateKeyPair() + content := newSK.Hex() + "\n" + if err := os.WriteFile(path, []byte(content), 0600); err != nil { + return fmt.Errorf("failed to write keyfile %s: %w", path, err) + } + *sk = cipher.SecKey(newSK) + fmt.Printf("Generated new keypair in %s\nPublic key: %s\n", path, pk.Hex()) + return nil +} diff --git a/vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cmdutil/service_flags.go b/vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cmdutil/service_flags.go index e8823d42..88cf12d4 100644 --- a/vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cmdutil/service_flags.go +++ b/vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cmdutil/service_flags.go @@ -60,8 +60,7 @@ func (sf *ServiceFlags) Init(rootCmd *cobra.Command, defaultTag, defaultConf str // "library" defaults if sf.SyslogNet == "" { - // TODO (evanlinjin): Consider using tcp as syslog udp is legacy. - sf.SyslogNet = "udp" + sf.SyslogNet = "udp" // NOTE: ServiceFlags is only used by vendored dmsg commands, not skywire itself } if sf.LogLevel == "" { sf.LogLevel = "debug" diff --git a/vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cmdutil/skyenv.go b/vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cmdutil/skyenv.go new file mode 100644 index 00000000..853cc66d --- /dev/null +++ b/vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cmdutil/skyenv.go @@ -0,0 +1,179 @@ +// Package cmdutil pkg/cmdutil/skyenv.go +// Shared SKYENV config file evaluation helpers. +// Used by skywire cli config gen and dmsg commands. +package cmdutil + +import ( + "fmt" + "strconv" + "strings" + + "github.com/bitfield/script" + + "github.com/skycoin/skywire/pkg/skyenv" +) + +// SkyenvValue sources a SKYENV file and evaluates a bash variable expression, +// returning the raw string result. This is the common core for all typed helpers. +func SkyenvValue(expr, envfile string) (string, error) { + if skyenv.OS == "windows" { + variable := expr + if strings.Contains(variable, ":-") { + parts := strings.SplitN(variable, ":-", 2) + variable = parts[0] + "}" + } + out, err := script.Exec(fmt.Sprintf( + `powershell -c "$SKYENV = '%s'; if ($SKYENV -ne '' -and (Test-Path $SKYENV)) { . $SKYENV }; echo %s"`, + envfile, variable, + )).String() + if err != nil { + return "", err + } + out = strings.TrimSpace(out) + if out == "" || out == variable { + return "", nil + } + return out, nil + } + out, err := script.Exec(fmt.Sprintf( + `bash -c 'SKYENV=%s ; if [[ $SKYENV != "" ]] && [[ -f $SKYENV ]] ; then source $SKYENV ; fi ; printf "%s"'`, + envfile, expr, + )).String() + if err != nil { + return "", err + } + return strings.TrimSpace(out), nil +} + +// SkyenvSlice sources a SKYENV file and expands a bash array expression +// into a string slice, one element per line. +func SkyenvSlice(expr, envfile string) ([]string, error) { + if skyenv.OS == "windows" { + variable := expr + if idx := strings.Index(variable, "[@]}"); idx != -1 { + variable = strings.TrimSuffix(variable, "[@]}") + variable = strings.TrimSuffix(variable, "{") + } + return script.Exec(fmt.Sprintf( + `powershell -c "$SKYENV = '%s'; if ($SKYENV -ne '' -and (Test-Path $SKYENV)) { . $SKYENV }; foreach ($item in %s) { Write-Host $item }"`, + envfile, variable, + )).Slice() + } + return script.Exec(fmt.Sprintf( + `bash -c 'SKYENV=%s ; if [[ $SKYENV != "" ]] && [[ -f $SKYENV ]] ; then source $SKYENV ; fi ; for _i in %s ; do echo "$_i" ; done'`, + envfile, expr, + )).Slice() +} + +// SkyenvDefault extracts the default value from a bash ${VAR:-default} expression. +func SkyenvDefault(s string) string { + if strings.Contains(s, ":-") { + parts := strings.SplitN(s, ":-", 2) + return strings.TrimRight(parts[1], "}") + } + return "" +} + +// SkyenvString evaluates a bash variable expression and returns the string value, +// falling back to the default from the expression if unset. +func SkyenvString(s, envfile string) string { + out, err := SkyenvValue(s, envfile) + if err != nil || out == "" { + return SkyenvDefault(s) + } + return out +} + +// SkyenvBool evaluates a bash variable expression and returns a bool. +func SkyenvBool(s, envfile string) bool { + out, err := SkyenvValue(s, envfile) + if err != nil || out == "" { + out = SkyenvDefault(s) + } + b, err := strconv.ParseBool(out) + if err != nil { + return false + } + return b +} + +// SkyenvArray evaluates a bash array expression and returns a comma-separated string. +func SkyenvArray(s, envfile string) string { + items, err := SkyenvSlice(s, envfile) + if err != nil || len(items) == 0 { + return "" + } + return strings.Join(items, ",") +} + +// SkyenvInt evaluates a bash variable expression and returns an int. +func SkyenvInt(s, envfile string) int { + out, err := SkyenvValue(s, envfile) + if err != nil || out == "" { + out = SkyenvDefault(s) + } + i, err := strconv.Atoi(out) + if err != nil { + return 0 + } + return i +} + +// SkyenvUint evaluates a bash variable expression and returns a uint. +func SkyenvUint(s, envfile string) uint { + out, err := SkyenvValue(s, envfile) + if err != nil || out == "" { + out = SkyenvDefault(s) + } + i, err := strconv.ParseUint(out, 10, 64) + if err != nil { + return 0 + } + return uint(i) +} + +// SkyenvStringSlice evaluates a bash array expression and returns a string slice. +func SkyenvStringSlice(s, envfile string) []string { + items, err := SkyenvSlice(s, envfile) + if err != nil || len(items) == 0 { + return []string{} + } + return items +} + +// SkyenvBoolSlice evaluates a bash array expression and returns a bool slice. +func SkyenvBoolSlice(s, envfile string) []bool { + items, err := SkyenvSlice(s, envfile) + if err != nil { + return []bool{false} + } + result := make([]bool, 0, len(items)) + for _, item := range items { + switch strings.ToLower(strings.TrimSpace(item)) { + case "true": + result = append(result, true) + default: + result = append(result, false) + } + } + if len(result) == 0 { + return []bool{false} + } + return result +} + +// SkyenvUintSlice evaluates a bash array expression and returns a uint slice. +func SkyenvUintSlice(s, envfile string) []uint { + items, err := SkyenvSlice(s, envfile) + if err != nil { + return []uint{} + } + result := make([]uint, 0, len(items)) + for _, item := range items { + num, err := strconv.ParseUint(strings.TrimSpace(item), 10, 64) + if err == nil { + result = append(result, uint(num)) + } + } + return result +} diff --git a/vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/httputil/health.go b/vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/httputil/health.go index 09d9f6e2..12a2c9f9 100644 --- a/vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/httputil/health.go +++ b/vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/httputil/health.go @@ -25,8 +25,12 @@ type HealthCheckResponse struct { } // GetServiceHealth gets the response from the given service url -func GetServiceHealth(_ context.Context, url string) (health *HealthCheckResponse, err error) { - resp, err := http.Get(url + path) +func GetServiceHealth(ctx context.Context, url string) (health *HealthCheckResponse, err error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url+path, nil) + if err != nil { + return nil, err + } + resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } diff --git a/vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/metricsutil/http.go b/vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/metricsutil/http.go index a6f863a5..de74cdff 100644 --- a/vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/metricsutil/http.go +++ b/vendor/github.com/skycoin/skywire/pkg/skywire-utilities/pkg/metricsutil/http.go @@ -3,7 +3,10 @@ package metricsutil import ( + "fmt" "net/http" + "net/http/pprof" + "time" "github.com/VictoriaMetrics/metrics" "github.com/go-chi/chi/v5" @@ -18,6 +21,49 @@ func AddMetricsHandler(mux *chi.Mux) { }) } +// ServePProf starts a pprof debug server on the given address. +// The serviceName is exposed at /debug/service so operators can identify +// which service a pprof endpoint belongs to. +func ServePProf(log logrus.FieldLogger, addr, serviceName string) { + if addr == "" { + return + } + + mux := http.NewServeMux() + + // Service identifier + mux.HandleFunc("/debug/service", func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "text/plain") + fmt.Fprintln(w, serviceName) //nolint:errcheck + }) + + // Standard pprof handlers + mux.HandleFunc("/debug/pprof/", pprof.Index) + mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + mux.HandleFunc("/debug/pprof/profile", pprof.Profile) + mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + mux.HandleFunc("/debug/pprof/trace", pprof.Trace) + + for _, profile := range []string{"heap", "goroutine", "threadcreate", "block", "mutex", "allocs"} { + mux.Handle("/debug/pprof/"+profile, pprof.Handler(profile)) + } + + go func() { + log.WithField("addr", addr).WithField("service", serviceName).Info("Starting pprof server") + server := &http.Server{ + Addr: addr, + Handler: mux, + ReadHeaderTimeout: 10 * time.Second, + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, + IdleTimeout: 60 * time.Second, + } + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.WithError(err).Error("pprof server failed") + } + }() +} + // ServeHTTPMetrics starts serving metrics on a given `addr`. func ServeHTTPMetrics(log logrus.FieldLogger, addr string) { if addr == "" { diff --git a/vendor/modules.txt b/vendor/modules.txt index 22092302..1ff107b5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -320,7 +320,7 @@ github.com/sirupsen/logrus/hooks/syslog # github.com/skycoin/noise v0.0.0-20180327030543-2492fe189ae6 ## explicit github.com/skycoin/noise -# github.com/skycoin/skycoin v0.28.6-0.20260328152706-a360adb0a4d3 +# github.com/skycoin/skycoin v0.28.6-0.20260401142608-a27afbb0b33b ## explicit; go 1.26.1 github.com/skycoin/skycoin/src/cipher github.com/skycoin/skycoin/src/cipher/base58 @@ -329,9 +329,10 @@ github.com/skycoin/skycoin/src/cipher/ripemd160 github.com/skycoin/skycoin/src/cipher/secp256k1-go github.com/skycoin/skycoin/src/cipher/secp256k1-go/secp256k1-go2 github.com/skycoin/skycoin/src/util/logging -# github.com/skycoin/skywire v1.3.40-0.20260328171146-a5facdc74e72 -## explicit; go 1.26.1 +# github.com/skycoin/skywire v1.3.40-0.20260402164617-90c6d9b52e01 +## explicit; go 1.21 github.com/skycoin/skywire/deployment +github.com/skycoin/skywire/pkg/skyenv github.com/skycoin/skywire/pkg/skywire-utilities/pkg/buildinfo github.com/skycoin/skywire/pkg/skywire-utilities/pkg/calvin github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cipher @@ -484,8 +485,6 @@ golang.org/x/text/secure/bidirule golang.org/x/text/transform golang.org/x/text/unicode/bidi golang.org/x/text/unicode/norm -# google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 -## explicit; go 1.25.0 # google.golang.org/protobuf v1.36.11 ## explicit; go 1.23 google.golang.org/protobuf/encoding/protowire From 8f89bd6e48145419fbb9e384759986ef75a2ee92 Mon Sep 17 00:00:00 2001 From: Moses Narrow <36607567+0pcom@users.noreply.github.com> Date: Thu, 2 Apr 2026 12:26:12 -0500 Subject: [PATCH 3/4] Add conf file integration tests for dmsgweb and dmsgweb srv --- cmd/dmsgweb/commands/conf_test.go | 123 ++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 cmd/dmsgweb/commands/conf_test.go diff --git a/cmd/dmsgweb/commands/conf_test.go b/cmd/dmsgweb/commands/conf_test.go new file mode 100644 index 00000000..49ed3800 --- /dev/null +++ b/cmd/dmsgweb/commands/conf_test.go @@ -0,0 +1,123 @@ +package commands + +import ( + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" +) + +// TestDmsgwebConfFile verifies that dmsgweb reads defaults from a conf file +// specified via the DMSGWEB env var and reflects them in --help output. +func TestDmsgwebConfFile(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("bash-based conf file not applicable on Windows") + } + + dir := t.TempDir() + confPath := filepath.Join(dir, "dmsgweb-test.conf") + confContent := `WEBPORT=(9090 9091) +PROXYPORT=5555 +ADDPROXY='127.0.0.1:1080' +` + if err := os.WriteFile(confPath, []byte(confContent), 0600); err != nil { + t.Fatal(err) + } + + // Run dmsgweb --help with the conf file + cmd := exec.Command("go", "run", "./cmd/dmsgweb", "--help") + cmd.Dir = repoRoot(t) + cmd.Env = append(os.Environ(), "DMSGWEB="+confPath) + out, err := cmd.CombinedOutput() + if err != nil { + // --help exits with code 0 normally, but some cobra versions exit non-zero + if !strings.Contains(string(out), "Usage:") { + t.Fatalf("dmsgweb --help failed: %v\noutput: %s", err, out) + } + } + + helpText := string(out) + + // Check that conf file values appear as defaults in help output + checks := []struct { + desc string + substr string + }{ + {"WEBPORT should be 9090,9091", "[9090,9091]"}, + {"PROXYPORT should be 5555", "5555"}, + {"ADDPROXY should be 127.0.0.1:1080", "127.0.0.1:1080"}, + } + + for _, c := range checks { + if !strings.Contains(helpText, c.substr) { + t.Errorf("%s: expected %q in help output, not found.\nHelp output:\n%s", c.desc, c.substr, helpText) + } + } +} + +// TestDmsgwebSrvConfFile verifies that dmsgweb srv reads defaults from a conf file +// specified via the DMSGWEBSRV env var and reflects them in --help output. +func TestDmsgwebSrvConfFile(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("bash-based conf file not applicable on Windows") + } + + dir := t.TempDir() + confPath := filepath.Join(dir, "dmsgwebsrv-test.conf") + confContent := `DMSGPORT=(8888 8889) +LOCALPORT=(7070 7071) +WHITELISTPKS=('02a49bc0aa1b5b78f638e9189be4c5d699e6d1358472d8a47f4c20daacd672d7e5') +` + if err := os.WriteFile(confPath, []byte(confContent), 0600); err != nil { + t.Fatal(err) + } + + // Run dmsgweb srv --help with the conf file + cmd := exec.Command("go", "run", "./cmd/dmsgweb", "srv", "--help") + cmd.Dir = repoRoot(t) + cmd.Env = append(os.Environ(), "DMSGWEBSRV="+confPath) + out, err := cmd.CombinedOutput() + if err != nil { + if !strings.Contains(string(out), "Usage:") { + t.Fatalf("dmsgweb srv --help failed: %v\noutput: %s", err, out) + } + } + + helpText := string(out) + + checks := []struct { + desc string + substr string + }{ + {"DMSGPORT should be 8888,8889", "[8888,8889]"}, + {"LOCALPORT should be 7070,7071", "[7070,7071]"}, + {"WHITELISTPKS should contain the PK", "02a49bc0aa1b5b78"}, + } + + for _, c := range checks { + if !strings.Contains(helpText, c.substr) { + t.Errorf("%s: expected %q in help output, not found.\nHelp output:\n%s", c.desc, c.substr, helpText) + } + } +} + +// repoRoot walks up from cwd to find go.mod +func repoRoot(t *testing.T) string { + t.Helper() + dir, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + for { + if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { + return dir + } + parent := filepath.Dir(dir) + if parent == dir { + t.Fatal("could not find repo root (go.mod)") + } + dir = parent + } +} From 536fb3d7d655154f29b086cae9271736e7c51f09 Mon Sep 17 00:00:00 2001 From: Moses Narrow <36607567+0pcom@users.noreply.github.com> Date: Thu, 2 Apr 2026 12:34:50 -0500 Subject: [PATCH 4/4] Fix dmsghttp test hang: add timeouts to result reads and client ready --- pkg/dmsghttp/http_transport_test.go | 36 +++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/pkg/dmsghttp/http_transport_test.go b/pkg/dmsghttp/http_transport_test.go index cc691c5b..d0b40158 100644 --- a/pkg/dmsghttp/http_transport_test.go +++ b/pkg/dmsghttp/http_transport_test.go @@ -103,11 +103,33 @@ func TestHTTPTransport_RoundTrip(t *testing.T) { } // Assert: ensure we get expected behavior from both the http client and server perspectives. + // Use a deadline to prevent hanging if a DMSG session fails. + deadline := time.After(60 * time.Second) for i := 0; i < nReqs; i++ { - (<-server0Results).Assert(t, i) - (<-client1Results).Assert(t, i) - (<-client2Results).Assert(t, i) - (<-client3Results).Assert(t, i) + select { + case r := <-server0Results: + r.Assert(t, i) + case <-deadline: + t.Fatal("timed out waiting for server results") + } + select { + case r := <-client1Results: + r.Assert(t, i) + case <-deadline: + t.Fatal("timed out waiting for client1 results") + } + select { + case r := <-client2Results: + r.Assert(t, i) + case <-deadline: + t.Fatal("timed out waiting for client2 results") + } + select { + case r := <-client3Results: + r.Assert(t, i) + case <-deadline: + t.Fatal("timed out waiting for client3 results") + } } }) } @@ -159,6 +181,10 @@ func newDmsgClient(t *testing.T, dc disc.APIClient, minSessions int, name string assert.NoError(t, dmsgC.Close()) }) - <-dmsgC.Ready() + select { + case <-dmsgC.Ready(): + case <-time.After(30 * time.Second): + t.Fatal("timed out waiting for dmsg client to be ready") + } return dmsgC }