From a4ee49a3de4b413e170a76f7d30ddc2b2dc8fe4d Mon Sep 17 00:00:00 2001
From: Moses Narrow <36607567+0pcom@users.noreply.github.com>
Date: Sun, 12 Mar 2023 12:07:36 -0500
Subject: [PATCH 01/19] move conwstants from visorconfig to skyenv package ;
 small changes on skywire-cli rtfind to print help on no arguments or use the
 local / running visor's public key as the first argument if one argument is
 given

---
 cmd/skywire-cli/commands/config/auto.go       |   3 +-
 cmd/skywire-cli/commands/root.go              |   2 +-
 cmd/skywire-cli/commands/rtfind/root.go       |  63 +++++-
 cmd/skywire-cli/commands/skysocksc/root.go    |  12 --
 .../commands/skysocksc/skysocksc.go           | 122 -----------
 cmd/skywire-cli/commands/vpn/root.go          |   2 +-
 cmd/skywire-cli/commands/vpn/vvpn.go          |   4 +-
 go.mod                                        |   1 -
 go.sum                                        |   2 -
 pkg/skyenv/skyenv.go                          |  12 +-
 pkg/visor/visorconfig/values.go               |  88 +++++++-
 pkg/visor/visorconfig/values_darwin.go        |  92 +-------
 pkg/visor/visorconfig/values_linux.go         | 121 +----------
 pkg/visor/visorconfig/values_windows.go       | 103 +--------
 vendor/modules.txt                            |   3 -
 vendor/periph.io/x/periph/AUTHORS             |  15 --
 vendor/periph.io/x/periph/CONTRIBUTORS        |  41 ----
 vendor/periph.io/x/periph/LICENSE             | 202 ------------------
 .../periph.io/x/periph/host/distro/devtree.go |  84 --------
 .../periph.io/x/periph/host/distro/distro.go  | 189 ----------------
 .../x/periph/host/distro/distro_arm.go        |   7 -
 .../x/periph/host/distro/distro_arm64.go      |   9 -
 .../x/periph/host/distro/distro_linux.go      |   7 -
 .../x/periph/host/distro/distro_nonarm.go     |   9 -
 .../x/periph/host/distro/distro_nonlinux.go   |   9 -
 25 files changed, 172 insertions(+), 1030 deletions(-)
 delete mode 100644 cmd/skywire-cli/commands/skysocksc/root.go
 delete mode 100644 cmd/skywire-cli/commands/skysocksc/skysocksc.go
 delete mode 100644 vendor/periph.io/x/periph/AUTHORS
 delete mode 100644 vendor/periph.io/x/periph/CONTRIBUTORS
 delete mode 100644 vendor/periph.io/x/periph/LICENSE
 delete mode 100644 vendor/periph.io/x/periph/host/distro/devtree.go
 delete mode 100644 vendor/periph.io/x/periph/host/distro/distro.go
 delete mode 100644 vendor/periph.io/x/periph/host/distro/distro_arm.go
 delete mode 100644 vendor/periph.io/x/periph/host/distro/distro_arm64.go
 delete mode 100644 vendor/periph.io/x/periph/host/distro/distro_linux.go
 delete mode 100644 vendor/periph.io/x/periph/host/distro/distro_nonarm.go
 delete mode 100644 vendor/periph.io/x/periph/host/distro/distro_nonlinux.go

diff --git a/cmd/skywire-cli/commands/config/auto.go b/cmd/skywire-cli/commands/config/auto.go
index fd6ada13fd..1ddaf4beca 100644
--- a/cmd/skywire-cli/commands/config/auto.go
+++ b/cmd/skywire-cli/commands/config/auto.go
@@ -1,5 +1,4 @@
-//go:build linux
-// +build linux
+//go:build exclude
 
 // Package cliconfig cmd/skywire-cli/commands/config/auto.go
 package cliconfig
diff --git a/cmd/skywire-cli/commands/root.go b/cmd/skywire-cli/commands/root.go
index 57dd9328f8..dae3699456 100644
--- a/cmd/skywire-cli/commands/root.go
+++ b/cmd/skywire-cli/commands/root.go
@@ -18,11 +18,11 @@ import (
 	clidmsgpty "github.com/skycoin/skywire/cmd/skywire-cli/commands/dmsgpty"
 	clilog "github.com/skycoin/skywire/cmd/skywire-cli/commands/log"
 	climdisc "github.com/skycoin/skywire/cmd/skywire-cli/commands/mdisc"
+	cliskysocksc "github.com/skycoin/skywire/cmd/skywire-cli/commands/proxy"
 	clireward "github.com/skycoin/skywire/cmd/skywire-cli/commands/reward"
 	clirtfind "github.com/skycoin/skywire/cmd/skywire-cli/commands/rtfind"
 	cliskyfwd "github.com/skycoin/skywire/cmd/skywire-cli/commands/skyfwd"
 	cliskyrev "github.com/skycoin/skywire/cmd/skywire-cli/commands/skyrev"
-	cliskysocksc "github.com/skycoin/skywire/cmd/skywire-cli/commands/skysocksc"
 	clisurvey "github.com/skycoin/skywire/cmd/skywire-cli/commands/survey"
 	clivisor "github.com/skycoin/skywire/cmd/skywire-cli/commands/visor"
 	clivpn "github.com/skycoin/skywire/cmd/skywire-cli/commands/vpn"
diff --git a/cmd/skywire-cli/commands/rtfind/root.go b/cmd/skywire-cli/commands/rtfind/root.go
index c0418e4377..86873eaca2 100644
--- a/cmd/skywire-cli/commands/rtfind/root.go
+++ b/cmd/skywire-cli/commands/rtfind/root.go
@@ -4,6 +4,7 @@ package clirtfind
 import (
 	"fmt"
 	"net/http"
+	"os"
 	"time"
 
 	"github.com/spf13/cobra"
@@ -11,21 +12,25 @@ import (
 
 	"github.com/skycoin/skywire-utilities/pkg/cipher"
 	utilenv "github.com/skycoin/skywire-utilities/pkg/skyenv"
+	clirpc "github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc"
 	"github.com/skycoin/skywire/cmd/skywire-cli/internal"
 	"github.com/skycoin/skywire/pkg/routefinder/rfclient"
 	"github.com/skycoin/skywire/pkg/routing"
+	"github.com/skycoin/skywire/pkg/skyenv"
+	"github.com/skycoin/skywire/pkg/visor/visorconfig"
 )
 
 var frAddr string
 var frMinHops, frMaxHops uint16
 var timeout time.Duration
+var skywireconfig string
 
 func init() {
 	RootCmd.Flags().SortFlags = false
-	RootCmd.Flags().Uint16VarP(&frMinHops, "min-hops", "n", 1, "minimum hops")
-	RootCmd.Flags().Uint16VarP(&frMaxHops, "max-hops", "x", 1000, "maximum hops")
+	RootCmd.Flags().Uint16VarP(&frMinHops, "min", "n", 1, "minimum hops")
+	RootCmd.Flags().Uint16VarP(&frMaxHops, "max", "x", 1000, "maximum hops")
 	RootCmd.Flags().DurationVarP(&timeout, "timeout", "t", 10*time.Second, "request timeout")
-	RootCmd.Flags().StringVarP(&frAddr, "addr", "a", utilenv.RouteFinderAddr, "route finder service address\n")
+	RootCmd.Flags().StringVarP(&frAddr, "addr", "a", "", "route finder service address\n"+utilenv.RouteFinderAddr)
 	var helpflag bool
 	RootCmd.Flags().BoolVarP(&helpflag, "help", "h", false, "help for "+RootCmd.Use)
 	RootCmd.Flags().MarkHidden("help") //nolint
@@ -33,13 +38,56 @@ func init() {
 
 // RootCmd is the command that queries the route finder.
 var RootCmd = &cobra.Command{
-	Use:   "rtfind <public-key-visor-1> <public-key-visor-2>",
+	Use:   "rtfind <public-key> | <public-key-visor-1> <public-key-visor-2>",
 	Short: "Query the Route Finder",
-	Args:  cobra.MinimumNArgs(2),
+	Long: `Query the Route Finder
+Assumes the local visor public key as an argument if only one argument is given`,
+	// Args: cobra.MinimumNArgs(2),
 	Run: func(cmd *cobra.Command, args []string) {
-		rfc := rfclient.NewHTTP(frAddr, timeout, &http.Client{}, nil)
-
 		var srcPK, dstPK cipher.PubKey
+		var pk string
+		//print the help menu if no arguments
+		if len(args) == 0 {
+			cmd.Help() //nolint
+			os.Exit(0)
+		}
+		//set the routefinder address. It's not used as the default value to fix the display of the help command
+		if frAddr == "" {
+			frAddr = utilenv.RouteFinderAddr
+		}
+		//assume the local public key as the first argument if only 1 argument is given ; resize args array to 2 and move the first argument to the second one
+		if len(args) == 1 {
+			rpcClient, err := clirpc.Client(cmd.Flags())
+			if err == nil {
+				overview, err := rpcClient.Overview()
+				if err == nil {
+					pk = overview.PubKey.String()
+				}
+			}
+			if err != nil {
+				//visor is not running, try to get pk from config
+				_, err := os.Stat(skyenv.SkywirePath + "/" + skyenv.ConfigJSON)
+				if err == nil {
+					//default to using the package config
+					skywireconfig = skyenv.SkywirePath + "/" + skyenv.ConfigJSON
+				} else {
+					//check for default config in current dir
+					_, err := os.Stat(skyenv.ConfigName)
+					if err == nil {
+						//use skywire-config.json in current dir
+						skywireconfig = skyenv.ConfigName
+					}
+				}
+				conf, err := visorconfig.ReadFile(skywireconfig)
+				if err != nil {
+					internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Failed to read config: %v", err))
+				}
+				pk = conf.PK.Hex()
+			}
+			args = append(args[:1], args[0:]...)
+			copy(args, []string{pk})
+		}
+		rfc := rfclient.NewHTTP(frAddr, timeout, &http.Client{}, nil)
 		internal.Catch(cmd.Flags(), srcPK.Set(args[0]))
 		internal.Catch(cmd.Flags(), dstPK.Set(args[1]))
 		forward := [2]cipher.PubKey{srcPK, dstPK}
@@ -48,7 +96,6 @@ var RootCmd = &cobra.Command{
 		routes, err := rfc.FindRoutes(ctx, []routing.PathEdges{forward, backward},
 			&rfclient.RouteOptions{MinHops: frMinHops, MaxHops: frMaxHops})
 		internal.Catch(cmd.Flags(), err)
-
 		output := fmt.Sprintf("forward: %v\n reverse: %v", routes[forward][0], routes[backward][0])
 		outputJSON := struct {
 			Forward []routing.Hop `json:"forward"`
diff --git a/cmd/skywire-cli/commands/skysocksc/root.go b/cmd/skywire-cli/commands/skysocksc/root.go
deleted file mode 100644
index ecdb76e73d..0000000000
--- a/cmd/skywire-cli/commands/skysocksc/root.go
+++ /dev/null
@@ -1,12 +0,0 @@
-// Package skysocksc root.go
-package skysocksc
-
-import (
-	"github.com/spf13/cobra"
-)
-
-// RootCmd contains commands that interact with the skywire-visor
-var RootCmd = &cobra.Command{
-	Use:   "skysocksc",
-	Short: "controls for Skysocks client",
-}
diff --git a/cmd/skywire-cli/commands/skysocksc/skysocksc.go b/cmd/skywire-cli/commands/skysocksc/skysocksc.go
deleted file mode 100644
index 5a35d034f3..0000000000
--- a/cmd/skywire-cli/commands/skysocksc/skysocksc.go
+++ /dev/null
@@ -1,122 +0,0 @@
-// Package skysocksc cmd/skywire-cli/commands/skysocksc/skysocks.go
-package skysocksc
-
-import (
-	"bytes"
-	"fmt"
-	"os"
-	"text/tabwriter"
-	"time"
-
-	"github.com/spf13/cobra"
-
-	clirpc "github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc"
-	"github.com/skycoin/skywire/cmd/skywire-cli/internal"
-	"github.com/skycoin/skywire/pkg/app/appserver"
-)
-
-var pk string
-
-func init() {
-	RootCmd.PersistentFlags().StringVar(&clirpc.Addr, "rpc", "localhost:3435", "RPC server address")
-	RootCmd.AddCommand(
-		skysockscStartCmd,
-		skysockscStopCmd,
-		skysockscStatusCmd,
-	)
-	skysockscStartCmd.Flags().StringVar(&pk, "pk", "", "skysocks server public key")
-}
-
-var skysockscStartCmd = &cobra.Command{
-	Use:   "start",
-	Short: "start the skysocks-client",
-	Args:  cobra.MinimumNArgs(0),
-	Run: func(cmd *cobra.Command, args []string) {
-		rpcClient, err := clirpc.Client(cmd.Flags())
-		if err != nil {
-			os.Exit(1)
-		}
-		internal.Catch(cmd.Flags(), rpcClient.StartSkysocksClient(pk))
-		internal.PrintOutput(cmd.Flags(), nil, "Starting.")
-		startProcess := true
-		for startProcess {
-			time.Sleep(time.Second * 1)
-			internal.PrintOutput(cmd.Flags(), nil, ".")
-			states, err := rpcClient.Apps()
-			internal.Catch(cmd.Flags(), err)
-
-			type output struct {
-				AppError string `json:"app_error,omitempty"`
-			}
-
-			for _, state := range states {
-				if state.Name == "skysocks-client" {
-					if state.Status == appserver.AppStatusRunning {
-						startProcess = false
-						internal.PrintOutput(cmd.Flags(), nil, fmt.Sprintln("\nRunning!"))
-					}
-					if state.Status == appserver.AppStatusErrored {
-						startProcess = false
-						out := output{
-							AppError: state.DetailedStatus,
-						}
-						internal.PrintOutput(cmd.Flags(), out, fmt.Sprintln("\nError! > "+state.DetailedStatus))
-					}
-				}
-			}
-		}
-	},
-}
-
-var skysockscStopCmd = &cobra.Command{
-	Use:   "stop",
-	Short: "stop the skysocks-client",
-	Run: func(cmd *cobra.Command, _ []string) {
-		rpcClient, err := clirpc.Client(cmd.Flags())
-		if err != nil {
-			os.Exit(1)
-		}
-		internal.Catch(cmd.Flags(), rpcClient.StopSkysocksClient())
-		internal.PrintOutput(cmd.Flags(), "OK", fmt.Sprintln("OK"))
-	},
-}
-
-var skysockscStatusCmd = &cobra.Command{
-	Use:   "status",
-	Short: "skysocks-client status",
-	Run: func(cmd *cobra.Command, _ []string) {
-		rpcClient, err := clirpc.Client(cmd.Flags())
-		if err != nil {
-			os.Exit(1)
-		}
-		states, err := rpcClient.Apps()
-		internal.Catch(cmd.Flags(), err)
-
-		var b bytes.Buffer
-		w := tabwriter.NewWriter(&b, 0, 0, 5, ' ', tabwriter.TabIndent)
-		internal.Catch(cmd.Flags(), err)
-		type appState struct {
-			Status string `json:"status"`
-		}
-		var jsonAppStatus appState
-		for _, state := range states {
-			if state.Name == "skysocks-client" {
-
-				status := "stopped"
-				if state.Status == appserver.AppStatusRunning {
-					status = "running"
-				}
-				if state.Status == appserver.AppStatusErrored {
-					status = "errored"
-				}
-				jsonAppStatus = appState{
-					Status: status,
-				}
-				_, err = fmt.Fprintf(w, "%s\n", status)
-				internal.Catch(cmd.Flags(), err)
-			}
-		}
-		internal.Catch(cmd.Flags(), w.Flush())
-		internal.PrintOutput(cmd.Flags(), jsonAppStatus, b.String())
-	},
-}
diff --git a/cmd/skywire-cli/commands/vpn/root.go b/cmd/skywire-cli/commands/vpn/root.go
index 1e1c576952..2b3f460249 100644
--- a/cmd/skywire-cli/commands/vpn/root.go
+++ b/cmd/skywire-cli/commands/vpn/root.go
@@ -17,5 +17,5 @@ var (
 // RootCmd contains commands that interact with the skywire-visor
 var RootCmd = &cobra.Command{
 	Use:   "vpn",
-	Short: "controls for VPN client",
+	Short: "VPN client",
 }
diff --git a/cmd/skywire-cli/commands/vpn/vvpn.go b/cmd/skywire-cli/commands/vpn/vvpn.go
index 7bd0fb787e..ce3e662168 100644
--- a/cmd/skywire-cli/commands/vpn/vvpn.go
+++ b/cmd/skywire-cli/commands/vpn/vvpn.go
@@ -36,9 +36,9 @@ func init() {
 	if version == "unknown" {
 		version = ""
 	}
-	vpnUICmd.Flags().BoolVarP(&isPkg, "pkg", "p", false, "use package config path")
+	vpnUICmd.Flags().BoolVarP(&isPkg, "pkg", "p", false, "use package config path: "+visorconfig.SkywirePath)
 	vpnUICmd.Flags().StringVarP(&path, "config", "c", "", "config path")
-	vpnURLCmd.Flags().BoolVarP(&isPkg, "pkg", "p", false, "use package config path")
+	vpnURLCmd.Flags().BoolVarP(&isPkg, "pkg", "p", false, "use package config path: "+visorconfig.SkywirePath)
 	vpnURLCmd.Flags().StringVarP(&path, "config", "c", "", "config path")
 	vpnListCmd.Flags().BoolVarP(&isUnFiltered, "nofilter", "n", false, "provide unfiltered results")
 	vpnListCmd.Flags().StringVarP(&ver, "ver", "v", version, "filter results by version")
diff --git a/go.mod b/go.mod
index b5c17b6e2b..709f98edef 100644
--- a/go.mod
+++ b/go.mod
@@ -55,7 +55,6 @@ require (
 	github.com/spf13/pflag v1.0.5
 	github.com/zcalusic/sysinfo v0.9.5
 	golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde
-	periph.io/x/periph v3.6.8+incompatible
 )
 
 require (
diff --git a/go.sum b/go.sum
index edd435e1aa..2b764be36a 100644
--- a/go.sum
+++ b/go.sum
@@ -1143,8 +1143,6 @@ howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
 howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
 nhooyr.io/websocket v1.8.2 h1:LwdzfyyOZKtVFoXay6A39Acu03KmidSZ3YUUvPa13PA=
 nhooyr.io/websocket v1.8.2/go.mod h1:LiqdCg1Cu7TPWxEvPjPa0TGYxCsy4pHNTN9gGluwBpQ=
-periph.io/x/periph v3.6.8+incompatible h1:lki0ie6wHtvlilXhIkabdCUQMpb5QN4Fx33yNQdqnaA=
-periph.io/x/periph v3.6.8+incompatible/go.mod h1:EWr+FCIU2dBWz5/wSWeiIUJTriYv9v2j2ENBmgYyy7Y=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
diff --git a/pkg/skyenv/skyenv.go b/pkg/skyenv/skyenv.go
index 4be52f5222..246613d9f6 100644
--- a/pkg/skyenv/skyenv.go
+++ b/pkg/skyenv/skyenv.go
@@ -114,9 +114,19 @@ const (
 	NodeInfoSha256 string = "node-info.sha"
 )
 
+// SkywireConfig returns the full path to the package config
+func SkywireConfig() string {
+	return SkywirePath + "/" + ConfigJSON
+}
+
+// SkyEnvs returns the full path to the environmental variable file
+func SkyEnvs() string {
+	return SkyenvFilePath + "/" + SkyenvFile
+}
+
 // PkgConfig struct contains paths specific to the installation
 type PkgConfig struct {
-	LauncherBinPath `json:"launcher"`
+	LauncherBinPath string `json:"launcher"`
 	LocalPath       string `json:"local_path"`
 	Hypervisor      `json:"hypervisor"`
 	//		TLSCertFile string `json:"tls_cert_file"`
diff --git a/pkg/visor/visorconfig/values.go b/pkg/visor/visorconfig/values.go
index bedc17370a..0b2772c7de 100644
--- a/pkg/visor/visorconfig/values.go
+++ b/pkg/visor/visorconfig/values.go
@@ -10,11 +10,15 @@ import (
 	"os/exec"
 	"os/user"
 	"path/filepath"
+	"runtime"
 	"strings"
 	"time"
 
 	"github.com/bitfield/script"
+	"github.com/google/uuid"
+	"github.com/jaypipes/ghw"
 	"github.com/skycoin/dmsg/pkg/dmsg"
+	"github.com/zcalusic/sysinfo"
 
 	"github.com/skycoin/skywire-utilities/pkg/buildinfo"
 	"github.com/skycoin/skywire-utilities/pkg/cipher"
@@ -22,6 +26,17 @@ import (
 )
 
 var (
+	//OS detection at runtime
+	OS = skyenv.OS
+	// SkywirePath is the path to the installation folder for the linux packages.
+	SkywirePath = skyenv.SkywirePath
+	// ConfigJSON is the config name generated by the skywire-autocofig script in the linux packages
+	ConfigJSON = skyenv.ConfigJSON
+	// SkyenvFilePath is the path to the SkyenvFile
+	SkyenvFilePath = skyenv.SkyenvFilePath
+	// SkyenvFile contains environmental variables which are detected by `skywire-autoconfig` / `skywire-cli config auto` to set default or persistent values
+	SkyenvFile = skyenv.SkyenvFile
+
 	// config file constants
 
 	// ConfigName is the default config name. Updated by setting config file path.
@@ -138,6 +153,11 @@ var (
 	RewardFile = skyenv.RewardFile
 )
 
+// SkywireConfig returns the full path to the package config
+func SkywireConfig() string {
+	return SkywirePath + "/" + ConfigJSON
+}
+
 // PkgConfig struct contains paths specific to the linux packages
 type PkgConfig struct {
 	LauncherBinPath string `json:"launcher"`
@@ -199,9 +219,9 @@ func HomePath() string {
 }
 
 // Config returns either UserConfig or PackageConfig based on permissions
-func Config() PkgConfig {
+func Config() skyenv.PkgConfig {
 	if IsRoot() {
-		return PackageConfig()
+		return skyenv.PackageConfig()
 	}
 	return UserConfig()
 }
@@ -304,3 +324,67 @@ var (
 	// VisorConfigFile will contain the path to the visor's config or `stdin` to denote that the config was read from STDIN
 	VisorConfigFile string
 )
+
+// Survey system hardware survey struct
+type Survey struct {
+	Timestamp      time.Time        `json:"timestamp"`
+	PubKey         cipher.PubKey    `json:"public_key,omitempty"`
+	SkycoinAddress string           `json:"skycoin_address,omitempty"`
+	GOOS           string           `json:"go_os,omitempty"`
+	GOARCH         string           `json:"go_arch,omitempty"`
+	SYSINFO        sysinfo.SysInfo  `json:"zcalusic_sysinfo,omitempty"`
+	IPInfo         *IPSkycoin       `json:"ip.skycoin.com,omitempty"`
+	IPAddr         *IPAddr          `json:"ip_addr,omitempty"`
+	Disks          *ghw.BlockInfo   `json:"ghw_blockinfo,omitempty"`
+	Product        *ghw.ProductInfo `json:"ghw_productinfo,omitempty"`
+	Memory         *ghw.MemoryInfo  `json:"ghw_memoryinfo,omitempty"`
+	UUID           uuid.UUID        `json:"uuid,omitempty"`
+	SkywireVersion string           `json:"skywire_version,omitempty"`
+}
+
+// SystemSurvey returns system survey
+func SystemSurvey() (Survey, error) {
+	var si sysinfo.SysInfo
+	si.GetSysInfo()
+	disks, err := ghw.Block()
+	if err != nil {
+		return Survey{}, err
+	}
+	product, err := ghw.Product()
+	if err != nil {
+		return Survey{}, err
+	}
+	memory, err := ghw.Memory()
+	if err != nil {
+		return Survey{}, err
+	}
+	for {
+		ipInfo := IPSkycoinFetch()
+		if ipInfo != nil {
+			break
+		}
+	}
+	s := Survey{
+		Timestamp:      time.Now(),
+		IPInfo:         IPSkycoinFetch(),
+		IPAddr:         IPA(),
+		GOOS:           runtime.GOOS,
+		GOARCH:         runtime.GOARCH,
+		SYSINFO:        si,
+		UUID:           uuid.New(),
+		Disks:          disks,
+		Product:        product,
+		Memory:         memory,
+		SkywireVersion: Version(),
+	}
+	return s, nil
+}
+
+func PackageConfig() skyenv.PkgConfig {
+	return skyenv.PackageConfig()
+}
+
+// UpdateCommand returns the commands which are run when the update button is clicked in the ui
+func UpdateCommand() []string {
+	return []string{`echo "not implemented"`}
+}
diff --git a/pkg/visor/visorconfig/values_darwin.go b/pkg/visor/visorconfig/values_darwin.go
index 1941314acd..06c2d49281 100644
--- a/pkg/visor/visorconfig/values_darwin.go
+++ b/pkg/visor/visorconfig/values_darwin.go
@@ -3,99 +3,17 @@
 
 package visorconfig
 
-import (
-	"runtime"
+import "github.com/skycoin/skywire/pkg/skyenv"
 
-	"github.com/google/uuid"
-	"github.com/jaypipes/ghw"
-
-	"github.com/skycoin/skywire-utilities/pkg/cipher"
-)
-
-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"
-
-	//TODO: @mrpalide set this correctly for macos. it shouldn't be in the installed path
-
-	// SkyenvFilePath is the path to the SkyenvFile
-	SkyenvFilePath = "/Library/Application Support/Skywire"
-	// SkyenvFile contains environmental variables which are detected by `skywire-autoconfig` / `skywire-cli config auto` to set default or persistent values
-	SkyenvFile = "skyenv.sh"
-)
-
-// SkywireConfig returns the full path to the package config
-func SkywireConfig() string {
-	return SkywirePath + "/" + ConfigJSON
-}
-
-// SkyEnvs returns the full path to the environmental variable file
-func SkyEnvs() string {
-	return SkyenvFilePath + "/" + SkyenvFile
-}
-
-// PackageConfig contains installation paths (for mac)
-func PackageConfig() PkgConfig {
-	pkgConfig := PkgConfig{
-		LauncherBinPath: "/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
-}
-
-// UserConfig contains installation paths (for mac)
-func UserConfig() PkgConfig {
-	usrConfig := PkgConfig{
+// UserConfig contains installation paths for running skywire as the user
+func UserConfig() skyenv.PkgConfig {
+	usrConfig := skyenv.PkgConfig{
 		LauncherBinPath: "/Applications/Skywire.app/Contents/MacOS/apps",
 		LocalPath:       HomePath() + "/.skywire/local",
-		Hypervisor: Hypervisor{
+		Hypervisor: skyenv.Hypervisor{
 			DbPath:     HomePath() + "/.skywire/users.db",
 			EnableAuth: true,
 		},
 	}
 	return usrConfig
 }
-
-// UpdateCommand returns the commands which are run when the update button is clicked in the ui
-func UpdateCommand() []string {
-	return []string{`echo "update not implemented for macOS. Download a new version from the release section here: https://github.com/skycoin/skywire/releases"`}
-}
-
-// Survey system hardware survey struct
-type Survey struct {
-	PubKey         cipher.PubKey  `json:"public_key,omitempty"`
-	SkycoinAddress string         `json:"skycoin_address,omitempty"`
-	GOOS           string         `json:"go_os,omitempty"`
-	GOARCH         string         `json:"go_arch,omitempty"`
-	IPInfo         *IPSkycoin     `json:"ip.skycoin.com,omitempty"`
-	IPAddr         *IPAddr        `json:"ip_addr,omitempty"`
-	Disks          *ghw.BlockInfo `json:"ghw_blockinfo,omitempty"`
-	UUID           uuid.UUID      `json:"uuid,omitempty"`
-	SkywireVersion string         `json:"skywire_version,omitempty"`
-}
-
-// SystemSurvey returns system survey
-func SystemSurvey() (Survey, error) {
-	disks, err := ghw.Block()
-	if err != nil {
-		return Survey{}, err
-	}
-	s := Survey{
-		IPInfo:         IPSkycoinFetch(),
-		IPAddr:         IPA(),
-		GOOS:           runtime.GOOS,
-		GOARCH:         runtime.GOARCH,
-		UUID:           uuid.New(),
-		Disks:          disks,
-		SkywireVersion: Version(),
-	}
-	return s, nil
-}
diff --git a/pkg/visor/visorconfig/values_linux.go b/pkg/visor/visorconfig/values_linux.go
index 8ca1095632..4ba55897da 100644
--- a/pkg/visor/visorconfig/values_linux.go
+++ b/pkg/visor/visorconfig/values_linux.go
@@ -3,128 +3,17 @@
 
 package visorconfig
 
-import (
-	"runtime"
-	"time"
+import "github.com/skycoin/skywire/pkg/skyenv"
 
-	"github.com/google/uuid"
-	"github.com/jaypipes/ghw"
-	"github.com/zcalusic/sysinfo"
-	"periph.io/x/periph/host/distro"
-
-	"github.com/skycoin/skywire-utilities/pkg/cipher"
-)
-
-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"
-	// SkyenvFilePath is the path to the SkyenvFile
-	SkyenvFilePath = "/etc/profile.d"
-	// SkyenvFile contains environmental variables which are detected by `skywire-autoconfig` / `skywire-cli config auto` to set default or persistent values
-	SkyenvFile = "skyenv.sh"
-)
-
-// SkywireConfig returns the full path to the package config
-func SkywireConfig() string {
-	return SkywirePath + "/" + ConfigJSON
-}
-
-// SkyEnvs returns the full path to the environmental variable file
-func SkyEnvs() string {
-	return SkyenvFilePath + "/" + SkyenvFile
-}
-
-// PackageConfig contains installation paths (for linux)
-func PackageConfig() PkgConfig {
-	pkgConfig := PkgConfig{
-		LauncherBinPath: "/opt/skywire/apps",
-		LocalPath:       "/opt/skywire/local",
-		Hypervisor: Hypervisor{
-			DbPath:     "/opt/skywire/users.db",
-			EnableAuth: true,
-		},
-	}
-	return pkgConfig
-}
-
-// UserConfig contains installation paths (for linux)
-func UserConfig() PkgConfig {
-	usrConfig := PkgConfig{
+// UserConfig contains installation paths for running skywire as the user
+func UserConfig() skyenv.PkgConfig {
+	usrConfig := skyenv.PkgConfig{
 		LauncherBinPath: "/opt/skywire/apps",
 		LocalPath:       HomePath() + "/.skywire/local",
-		Hypervisor: Hypervisor{
+		Hypervisor: skyenv.Hypervisor{
 			DbPath:     HomePath() + "/.skywire/users.db",
 			EnableAuth: true,
 		},
 	}
 	return usrConfig
 }
-
-// UpdateCommand returns the commands which are run when the update button is clicked in the ui
-func UpdateCommand() []string {
-	if distro.IsArmbian() || distro.IsDebian() || distro.IsRaspbian() || distro.IsUbuntu() {
-		//enabling install-skyire.service and rebooting is required to avoid interrupting an update when the running visor is stopped
-		//install-skywire.service is provided by the skybian package and calls install-skywire.sh
-		return []string{`systemctl enable install-skywire.service && systemctl reboot || echo -e "Resource unavailable.\nPlease update manually as specified here:\nhttps://github.com/skycoin/skywire/wiki/Skywire-Package-Installation"`}
-	}
-	return []string{`echo -e "Update not implemented for this linux distro.\nPlease update skywire the same way you installed it."`}
-}
-
-// Survey system hardware survey struct
-type Survey struct {
-	Timestamp      time.Time        `json:"timestamp"`
-	PubKey         cipher.PubKey    `json:"public_key,omitempty"`
-	SkycoinAddress string           `json:"skycoin_address,omitempty"`
-	GOOS           string           `json:"go_os,omitempty"`
-	GOARCH         string           `json:"go_arch,omitempty"`
-	SYSINFO        sysinfo.SysInfo  `json:"zcalusic_sysinfo,omitempty"`
-	IPInfo         *IPSkycoin       `json:"ip.skycoin.com,omitempty"`
-	IPAddr         *IPAddr          `json:"ip_addr,omitempty"`
-	Disks          *ghw.BlockInfo   `json:"ghw_blockinfo,omitempty"`
-	Product        *ghw.ProductInfo `json:"ghw_productinfo,omitempty"`
-	Memory         *ghw.MemoryInfo  `json:"ghw_memoryinfo,omitempty"`
-	UUID           uuid.UUID        `json:"uuid,omitempty"`
-	SkywireVersion string           `json:"skywire_version,omitempty"`
-}
-
-// SystemSurvey returns system survey
-func SystemSurvey() (Survey, error) {
-	var si sysinfo.SysInfo
-	si.GetSysInfo()
-	disks, err := ghw.Block()
-	if err != nil {
-		return Survey{}, err
-	}
-	product, err := ghw.Product()
-	if err != nil {
-		return Survey{}, err
-	}
-	memory, err := ghw.Memory()
-	if err != nil {
-		return Survey{}, err
-	}
-	for {
-		ipInfo := IPSkycoinFetch()
-		if ipInfo != nil {
-			break
-		}
-	}
-	s := Survey{
-		Timestamp:      time.Now(),
-		IPInfo:         IPSkycoinFetch(),
-		IPAddr:         IPA(),
-		GOOS:           runtime.GOOS,
-		GOARCH:         runtime.GOARCH,
-		SYSINFO:        si,
-		UUID:           uuid.New(),
-		Disks:          disks,
-		Product:        product,
-		Memory:         memory,
-		SkywireVersion: Version(),
-	}
-	return s, nil
-}
diff --git a/pkg/visor/visorconfig/values_windows.go b/pkg/visor/visorconfig/values_windows.go
index 0ec094c07f..10b904c2a6 100644
--- a/pkg/visor/visorconfig/values_windows.go
+++ b/pkg/visor/visorconfig/values_windows.go
@@ -3,110 +3,17 @@
 
 package visorconfig
 
-import (
-	"runtime"
+import "github.com/skycoin/skywire/pkg/skyenv"
 
-	"github.com/google/uuid"
-	"github.com/jaypipes/ghw"
-
-	"github.com/skycoin/skywire-utilities/pkg/cipher"
-)
-
-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"
-
-	//TODO: @mrpalide set this correctly for windows. it shouldn't be in the installed path
-
-	// SkyenvFilePath is the path to the SkyenvFile
-	SkyenvFilePath = "C:/Program Files/Skywire"
-	// SkyenvFile contains environmental variables which are detected by `skywire-autoconfig` / `skywire-cli config auto` to set default or persistent values
-	SkyenvFile = "skyenv.bat"
-)
-
-// SkywireConfig returns the full path to the package config
-func SkywireConfig() string {
-	return SkywirePath + "/" + ConfigJSON
-}
-
-// SkyEnvs returns the full path to the environmental variable file
-func SkyEnvs() string {
-	return SkyenvFilePath + "/" + SkyenvFile
-}
-
-// PackageConfig contains installation paths (for windows)
-func PackageConfig() PkgConfig {
-	pkgConfig := PkgConfig{
-		LauncherBinPath: "C:/Program Files/Skywire/apps",
-		LocalPath:       "C:/Program Files/Skywire/local",
-		Hypervisor: Hypervisor{
-			DbPath:     "C:/Program Files/Skywire/users.db",
-			EnableAuth: true,
-		},
-	}
-	return pkgConfig
-}
-
-// UserConfig contains installation paths (for windows)
-func UserConfig() PkgConfig {
-	usrConfig := PkgConfig{
+// UserConfig contains installation paths for running skywire as the user
+func UserConfig() skyenv.PkgConfig {
+	usrConfig := skyenv.PkgConfig{
 		LauncherBinPath: "C:/Program Files/Skywire/apps",
 		LocalPath:       HomePath() + "/.skywire/local",
-		Hypervisor: Hypervisor{
+		Hypervisor: skyenv.Hypervisor{
 			DbPath:     HomePath() + "/.skywire/users.db",
 			EnableAuth: true,
 		},
 	}
 	return usrConfig
 }
-
-// UpdateCommand returns the commands which are run when the update button is clicked in the ui
-func UpdateCommand() []string {
-	return []string{`echo "Update not implemented for windows. Download a new version from the release section here: https://github.com/skycoin/skywire/releases"`}
-}
-
-// Survey system hardware survey struct
-type Survey struct {
-	PubKey         cipher.PubKey    `json:"public_key,omitempty"`
-	SkycoinAddress string           `json:"skycoin_address,omitempty"`
-	GOOS           string           `json:"go_os,omitempty"`
-	GOARCH         string           `json:"go_arch,omitempty"`
-	IPInfo         *IPSkycoin       `json:"ip.skycoin.com,omitempty"`
-	IPAddr         *IPAddr          `json:"ip_addr,omitempty"`
-	Disks          *ghw.BlockInfo   `json:"ghw_blockinfo,omitempty"`
-	Product        *ghw.ProductInfo `json:"ghw_productinfo,omitempty"`
-	Memory         *ghw.MemoryInfo  `json:"ghw_memoryinfo,omitempty"`
-	UUID           uuid.UUID        `json:"uuid,omitempty"`
-	SkywireVersion string           `json:"skywire_version,omitempty"`
-}
-
-// SystemSurvey returns system survey
-func SystemSurvey() (Survey, error) {
-	disks, err := ghw.Block()
-	if err != nil {
-		return Survey{}, err
-	}
-	product, err := ghw.Product()
-	if err != nil {
-		return Survey{}, err
-	}
-	memory, err := ghw.Memory()
-	if err != nil {
-		return Survey{}, err
-	}
-	s := Survey{
-		IPInfo:         IPSkycoinFetch(),
-		GOOS:           runtime.GOOS,
-		GOARCH:         runtime.GOARCH,
-		UUID:           uuid.New(),
-		Disks:          disks,
-		Product:        product,
-		Memory:         memory,
-		SkywireVersion: Version(),
-	}
-	return s, nil
-}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 31d911259a..9c0e65d455 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -462,6 +462,3 @@ nhooyr.io/websocket/internal/bpool
 nhooyr.io/websocket/internal/errd
 nhooyr.io/websocket/internal/wsjs
 nhooyr.io/websocket/internal/xsync
-# periph.io/x/periph v3.6.8+incompatible
-## explicit
-periph.io/x/periph/host/distro
diff --git a/vendor/periph.io/x/periph/AUTHORS b/vendor/periph.io/x/periph/AUTHORS
deleted file mode 100644
index e8ff861edc..0000000000
--- a/vendor/periph.io/x/periph/AUTHORS
+++ /dev/null
@@ -1,15 +0,0 @@
-# This is the list of The Periph Authors for copyright purposes.
-#
-# This does not necessarily list everyone who has contributed code, since in
-# some cases, their employer may be the copyright holder.  To see the full list
-# of contributors, see the revision history in source control.
-Cássio Botaro <cassiobotaro@gmail.com>
-Fractal Industries, Inc
-Google Inc.
-Josh Gardiner
-Matt Aimonetti <mattaimonetti@gmail.com>
-Max Ekman <max@looplab.se>
-Rifiniti, Inc
-Stephan Sperber
-Thorsten von Eicken <tve@voneicken.com>
-
diff --git a/vendor/periph.io/x/periph/CONTRIBUTORS b/vendor/periph.io/x/periph/CONTRIBUTORS
deleted file mode 100644
index 2e86a9bcbc..0000000000
--- a/vendor/periph.io/x/periph/CONTRIBUTORS
+++ /dev/null
@@ -1,41 +0,0 @@
-# This is the official list of people who can contribute
-# (and typically have contributed) code to the periph repository.
-# The AUTHORS file lists the copyright holders; this file
-# lists people.  For example, Google employees are listed here
-# but not in AUTHORS, because Google holds the copyright.
-#
-# Names should be added to this file only after verifying that
-# the individual or the individual's organization has agreed to
-# the appropriate Contributor License Agreement, found here:
-#
-#     https://cla.developers.google.com/
-#
-# When adding J Random Contributor's name to this file,
-# either J's name or J's organization's name should be
-# added to the AUTHORS file, depending on whether the
-# individual or corporate CLA was used.
-
-# Names should be added to this file like so:
-#     Individual's name <submission email address>
-#     Individual's name <submission email address> <email2> <emailN>
-#
-# An entry with multiple email addresses specifies that the
-# first address should be used in the submit logs and
-# that the other addresses should be recognized as the
-# same person when interacting with Gerrit.
-
-# Please keep the list sorted.
-
-Cássio Botaro <cassiobotaro@gmail.com>
-Eugene Dzhurynsky <jdevelop@gmail.com>
-Hidetoshi Shimokawa <smkwhdts@gmail.com>
-John Maguire <john.maguire@gmail.com>
-Josh Gardiner <josh@zool.com>
-Marc-Antoine Ruel <maruel@chromium.org> <maruel@gmail.com>
-Matt Aimonetti <mattaimonetti@gmail.com>
-Max Ekman <max@looplab.se>
-Matias Insaurralde <matias@insaurral.de>
-Seán C McCord <ulexus@gmail.com> <scm@cycoresys.com>
-Stephan Sperber <sperberstephan@googlemail.com>
-Thorsten von Eicken <tve@voneicken.com>
-
diff --git a/vendor/periph.io/x/periph/LICENSE b/vendor/periph.io/x/periph/LICENSE
deleted file mode 100644
index d645695673..0000000000
--- a/vendor/periph.io/x/periph/LICENSE
+++ /dev/null
@@ -1,202 +0,0 @@
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
diff --git a/vendor/periph.io/x/periph/host/distro/devtree.go b/vendor/periph.io/x/periph/host/distro/devtree.go
deleted file mode 100644
index e8c2da4389..0000000000
--- a/vendor/periph.io/x/periph/host/distro/devtree.go
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2016 The Periph Authors. All rights reserved.
-// Use of this source code is governed under the Apache License, Version 2.0
-// that can be found in the LICENSE file.
-
-package distro
-
-import (
-	"encoding/binary"
-	"io/ioutil"
-)
-
-// DTModel returns platform model info from the Linux device tree (/proc/device-tree/model), and
-// returns "unknown" on non-linux systems or if the file is missing.
-func DTModel() string {
-	mu.Lock()
-	defer mu.Unlock()
-
-	if dtModel == "" {
-		dtModel = "<unknown>"
-		if isLinux {
-			dtModel = makeDTModelLinux()
-		}
-	}
-	return dtModel
-}
-
-// DTCompatible returns platform compatibility info from the Linux device tree
-// (/proc/device-tree/compatible), and returns []{"unknown"} on non-linux systems or if the file is
-// missing.
-func DTCompatible() []string {
-	mu.Lock()
-	defer mu.Unlock()
-
-	if dtCompatible == nil {
-		dtCompatible = []string{}
-		if isLinux {
-			dtCompatible = makeDTCompatible()
-		}
-	}
-	return dtCompatible
-}
-
-// DTRevision returns the device revision (e.g. a02082 for the Raspberry Pi 3)
-// from the Linux device tree, or 0 if the file is missing or malformed.
-func DTRevision() uint32 {
-	mu.Lock()
-	defer mu.Unlock()
-
-	if dtRevisionRead {
-		return dtRevision
-	}
-	dtRevisionRead = true
-	if b, _ := ioutil.ReadFile("/proc/device-tree/system/linux,revision"); len(b) >= 4 {
-		dtRevision = binary.BigEndian.Uint32(b[:4])
-	}
-	return dtRevision
-}
-
-//
-
-var (
-	dtModel        string   // cached /proc/device-tree/model
-	dtCompatible   []string // cached /proc/device-tree/compatible
-	dtRevision     uint32   // cached /proc/device-tree/system/linux,revision
-	dtRevisionRead bool
-)
-
-func makeDTModelLinux() string {
-	// Read model from device tree.
-	if bytes, err := readFile("/proc/device-tree/model"); err == nil {
-		if model := splitNull(bytes); len(model) > 0 {
-			return model[0]
-		}
-	}
-	return "<unknown>"
-}
-
-func makeDTCompatible() []string {
-	// Read compatible from device tree.
-	if bytes, err := readFile("/proc/device-tree/compatible"); err == nil {
-		return splitNull(bytes)
-	}
-	return []string{}
-}
diff --git a/vendor/periph.io/x/periph/host/distro/distro.go b/vendor/periph.io/x/periph/host/distro/distro.go
deleted file mode 100644
index 96e49dfbf9..0000000000
--- a/vendor/periph.io/x/periph/host/distro/distro.go
+++ /dev/null
@@ -1,189 +0,0 @@
-// Copyright 2016 The Periph Authors. All rights reserved.
-// Use of this source code is governed under the Apache License, Version 2.0
-// that can be found in the LICENSE file.
-
-// Package distro implements common functionality to auto-detect features on
-// the host; generally about linux distributions.
-//
-// Most of the functions exported as in the form IsFoo() where Foo is a linux
-// distribution.
-package distro
-
-import (
-	"io/ioutil"
-	"os"
-	"strconv"
-	"strings"
-	"sync"
-	"unicode"
-)
-
-// IsArmbian returns true if running on a Armbian distribution.
-//
-// http://www.armbian.com/
-func IsArmbian() bool {
-	if isArm && isLinux {
-		// Armbian presents itself as debian in /etc/os-release so OSRelease()
-		// cannot be used..
-		_, err := os.Stat("/etc/armbian.txt")
-		return err == nil
-	}
-	return false
-}
-
-// IsDebian returns true if running on an Debian derived distribution.
-//
-// This function returns true on both Armbian, Raspbian and Ubuntu.
-//
-// https://debian.org/
-func IsDebian() bool {
-	if isLinux {
-		// http://0pointer.de/public/systemd-man/os-release.html#ID_LIKE=
-		if OSRelease()["ID"] == "debian" {
-			return true
-		}
-		for _, part := range strings.Split(OSRelease()["ID_LIKE"], " ") {
-			if part == "debian" {
-				return true
-			}
-		}
-	}
-	return false
-}
-
-// IsRaspbian returns true if running on a Raspbian distribution.
-//
-// https://raspbian.org/
-func IsRaspbian() bool {
-	if isArm && isLinux {
-		return OSRelease()["ID"] == "raspbian"
-	}
-	return false
-}
-
-// IsUbuntu returns true if running on an Ubuntu derived distribution.
-//
-// https://ubuntu.com/
-func IsUbuntu() bool {
-	if isLinux {
-		return OSRelease()["ID"] == "ubuntu"
-	}
-	return false
-}
-
-// OSRelease returns parsed data from /etc/os-release.
-//
-// For more information, see
-// http://0pointer.de/public/systemd-man/os-release.html
-func OSRelease() map[string]string {
-	if isLinux {
-		return makeOSReleaseLinux()
-	}
-	return osRelease
-}
-
-// CPU
-
-// CPUInfo returns parsed data from /proc/cpuinfo.
-func CPUInfo() map[string]string {
-	if isLinux {
-		return makeCPUInfoLinux()
-	}
-	return cpuInfo
-}
-
-//
-
-var (
-	mu        sync.Mutex
-	cpuInfo   map[string]string
-	osRelease map[string]string
-	readFile  = ioutil.ReadFile
-)
-
-func splitSemiColon(content string) map[string]string {
-	// Strictly speaking this format isn't ok, there can be multiple group.
-	out := map[string]string{}
-	for _, line := range strings.Split(content, "\n") {
-		parts := strings.SplitN(line, ":", 2)
-		if len(parts) != 2 {
-			continue
-		}
-		// This format may have space around the ':'.
-		key := strings.TrimRightFunc(parts[0], unicode.IsSpace)
-		if len(key) == 0 || key[0] == '#' {
-			continue
-		}
-		// Ignore duplicate keys.
-		// TODO(maruel): Keep them all.
-		if _, ok := out[key]; !ok {
-			// Trim on both side, trailing space was observed on "Features" value.
-			out[key] = strings.TrimFunc(parts[1], unicode.IsSpace)
-		}
-	}
-	return out
-}
-
-func splitStrict(content string) map[string]string {
-	out := map[string]string{}
-	for _, line := range strings.Split(content, "\n") {
-		parts := strings.SplitN(line, "=", 2)
-		if len(parts) != 2 {
-			continue
-		}
-		key := parts[0]
-		if len(key) == 0 || key[0] == '#' {
-			continue
-		}
-		// Overwrite previous key.
-		value := parts[1]
-		if len(value) > 2 && value[0] == '"' && value[len(value)-1] == '"' {
-			// Not exactly 100% right but #closeenough. See for more details
-			// https://www.freedesktop.org/software/systemd/man/os-release.html
-			var err error
-			value, err = strconv.Unquote(value)
-			if err != nil {
-				continue
-			}
-		}
-		out[key] = value
-	}
-	return out
-}
-
-// splitNull returns the null-terminated strings in the data
-func splitNull(data []byte) []string {
-	ss := strings.Split(string(data), "\x00")
-	// The last string is typically null-terminated, so remove empty string
-	// from end of array.
-	if len(ss) > 0 && len(ss[len(ss)-1]) == 0 {
-		ss = ss[:len(ss)-1]
-	}
-	return ss
-}
-
-func makeCPUInfoLinux() map[string]string {
-	mu.Lock()
-	defer mu.Unlock()
-	if cpuInfo == nil {
-		cpuInfo = map[string]string{}
-		if bytes, err := readFile("/proc/cpuinfo"); err == nil {
-			cpuInfo = splitSemiColon(string(bytes))
-		}
-	}
-	return cpuInfo
-}
-
-func makeOSReleaseLinux() map[string]string {
-	mu.Lock()
-	defer mu.Unlock()
-	if osRelease == nil {
-		// This file may not exist on older distros. Send a PR if you want to have
-		// a specific fallback.
-		osRelease = map[string]string{}
-		if bytes, err := readFile("/etc/os-release"); err == nil {
-			osRelease = splitStrict(string(bytes))
-		}
-	}
-	return osRelease
-}
diff --git a/vendor/periph.io/x/periph/host/distro/distro_arm.go b/vendor/periph.io/x/periph/host/distro/distro_arm.go
deleted file mode 100644
index 90a23351f0..0000000000
--- a/vendor/periph.io/x/periph/host/distro/distro_arm.go
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright 2016 The Periph Authors. All rights reserved.
-// Use of this source code is governed under the Apache License, Version 2.0
-// that can be found in the LICENSE file.
-
-package distro
-
-const isArm = true
diff --git a/vendor/periph.io/x/periph/host/distro/distro_arm64.go b/vendor/periph.io/x/periph/host/distro/distro_arm64.go
deleted file mode 100644
index d5734bc040..0000000000
--- a/vendor/periph.io/x/periph/host/distro/distro_arm64.go
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright 2016 The Periph Authors. All rights reserved.
-// Use of this source code is governed under the Apache License, Version 2.0
-// that can be found in the LICENSE file.
-
-// +build arm64
-
-package distro
-
-const isArm = true
diff --git a/vendor/periph.io/x/periph/host/distro/distro_linux.go b/vendor/periph.io/x/periph/host/distro/distro_linux.go
deleted file mode 100644
index 7679a8fd84..0000000000
--- a/vendor/periph.io/x/periph/host/distro/distro_linux.go
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright 2016 The Periph Authors. All rights reserved.
-// Use of this source code is governed under the Apache License, Version 2.0
-// that can be found in the LICENSE file.
-
-package distro
-
-const isLinux = true
diff --git a/vendor/periph.io/x/periph/host/distro/distro_nonarm.go b/vendor/periph.io/x/periph/host/distro/distro_nonarm.go
deleted file mode 100644
index 075cad554c..0000000000
--- a/vendor/periph.io/x/periph/host/distro/distro_nonarm.go
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright 2016 The Periph Authors. All rights reserved.
-// Use of this source code is governed under the Apache License, Version 2.0
-// that can be found in the LICENSE file.
-
-// +build !arm,!arm64
-
-package distro
-
-const isArm = false
diff --git a/vendor/periph.io/x/periph/host/distro/distro_nonlinux.go b/vendor/periph.io/x/periph/host/distro/distro_nonlinux.go
deleted file mode 100644
index 7fd1599e08..0000000000
--- a/vendor/periph.io/x/periph/host/distro/distro_nonlinux.go
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright 2016 The Periph Authors. All rights reserved.
-// Use of this source code is governed under the Apache License, Version 2.0
-// that can be found in the LICENSE file.
-
-// +build !linux
-
-package distro
-
-const isLinux = false

From f435ade4737b4ebd21f658f08e4d73fdea7677a0 Mon Sep 17 00:00:00 2001
From: Moses Narrow <36607567+0pcom@users.noreply.github.com>
Date: Sun, 12 Mar 2023 12:08:44 -0500
Subject: [PATCH 02/19] change skywire-cli skysocksc to skywire-cli proxy

---
 cmd/skywire-cli/commands/proxy/proxy.go | 122 ++++++++++++++++++++++++
 cmd/skywire-cli/commands/proxy/root.go  |  12 +++
 2 files changed, 134 insertions(+)
 create mode 100644 cmd/skywire-cli/commands/proxy/proxy.go
 create mode 100644 cmd/skywire-cli/commands/proxy/root.go

diff --git a/cmd/skywire-cli/commands/proxy/proxy.go b/cmd/skywire-cli/commands/proxy/proxy.go
new file mode 100644
index 0000000000..5a35d034f3
--- /dev/null
+++ b/cmd/skywire-cli/commands/proxy/proxy.go
@@ -0,0 +1,122 @@
+// Package skysocksc cmd/skywire-cli/commands/skysocksc/skysocks.go
+package skysocksc
+
+import (
+	"bytes"
+	"fmt"
+	"os"
+	"text/tabwriter"
+	"time"
+
+	"github.com/spf13/cobra"
+
+	clirpc "github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc"
+	"github.com/skycoin/skywire/cmd/skywire-cli/internal"
+	"github.com/skycoin/skywire/pkg/app/appserver"
+)
+
+var pk string
+
+func init() {
+	RootCmd.PersistentFlags().StringVar(&clirpc.Addr, "rpc", "localhost:3435", "RPC server address")
+	RootCmd.AddCommand(
+		skysockscStartCmd,
+		skysockscStopCmd,
+		skysockscStatusCmd,
+	)
+	skysockscStartCmd.Flags().StringVar(&pk, "pk", "", "skysocks server public key")
+}
+
+var skysockscStartCmd = &cobra.Command{
+	Use:   "start",
+	Short: "start the skysocks-client",
+	Args:  cobra.MinimumNArgs(0),
+	Run: func(cmd *cobra.Command, args []string) {
+		rpcClient, err := clirpc.Client(cmd.Flags())
+		if err != nil {
+			os.Exit(1)
+		}
+		internal.Catch(cmd.Flags(), rpcClient.StartSkysocksClient(pk))
+		internal.PrintOutput(cmd.Flags(), nil, "Starting.")
+		startProcess := true
+		for startProcess {
+			time.Sleep(time.Second * 1)
+			internal.PrintOutput(cmd.Flags(), nil, ".")
+			states, err := rpcClient.Apps()
+			internal.Catch(cmd.Flags(), err)
+
+			type output struct {
+				AppError string `json:"app_error,omitempty"`
+			}
+
+			for _, state := range states {
+				if state.Name == "skysocks-client" {
+					if state.Status == appserver.AppStatusRunning {
+						startProcess = false
+						internal.PrintOutput(cmd.Flags(), nil, fmt.Sprintln("\nRunning!"))
+					}
+					if state.Status == appserver.AppStatusErrored {
+						startProcess = false
+						out := output{
+							AppError: state.DetailedStatus,
+						}
+						internal.PrintOutput(cmd.Flags(), out, fmt.Sprintln("\nError! > "+state.DetailedStatus))
+					}
+				}
+			}
+		}
+	},
+}
+
+var skysockscStopCmd = &cobra.Command{
+	Use:   "stop",
+	Short: "stop the skysocks-client",
+	Run: func(cmd *cobra.Command, _ []string) {
+		rpcClient, err := clirpc.Client(cmd.Flags())
+		if err != nil {
+			os.Exit(1)
+		}
+		internal.Catch(cmd.Flags(), rpcClient.StopSkysocksClient())
+		internal.PrintOutput(cmd.Flags(), "OK", fmt.Sprintln("OK"))
+	},
+}
+
+var skysockscStatusCmd = &cobra.Command{
+	Use:   "status",
+	Short: "skysocks-client status",
+	Run: func(cmd *cobra.Command, _ []string) {
+		rpcClient, err := clirpc.Client(cmd.Flags())
+		if err != nil {
+			os.Exit(1)
+		}
+		states, err := rpcClient.Apps()
+		internal.Catch(cmd.Flags(), err)
+
+		var b bytes.Buffer
+		w := tabwriter.NewWriter(&b, 0, 0, 5, ' ', tabwriter.TabIndent)
+		internal.Catch(cmd.Flags(), err)
+		type appState struct {
+			Status string `json:"status"`
+		}
+		var jsonAppStatus appState
+		for _, state := range states {
+			if state.Name == "skysocks-client" {
+
+				status := "stopped"
+				if state.Status == appserver.AppStatusRunning {
+					status = "running"
+				}
+				if state.Status == appserver.AppStatusErrored {
+					status = "errored"
+				}
+				jsonAppStatus = appState{
+					Status: status,
+				}
+				_, err = fmt.Fprintf(w, "%s\n", status)
+				internal.Catch(cmd.Flags(), err)
+			}
+		}
+		internal.Catch(cmd.Flags(), w.Flush())
+		internal.PrintOutput(cmd.Flags(), jsonAppStatus, b.String())
+	},
+}
diff --git a/cmd/skywire-cli/commands/proxy/root.go b/cmd/skywire-cli/commands/proxy/root.go
new file mode 100644
index 0000000000..e981153cf9
--- /dev/null
+++ b/cmd/skywire-cli/commands/proxy/root.go
@@ -0,0 +1,12 @@
+// Package skysocksc root.go
+package skysocksc
+
+import (
+	"github.com/spf13/cobra"
+)
+
+// RootCmd contains commands that interact with the skywire-visor
+var RootCmd = &cobra.Command{
+	Use:   "proxy",
+	Short: "Skysocks client",
+}

From ed4ea31c40bfe1c318867802c3cdf20882d667d2 Mon Sep 17 00:00:00 2001
From: Moses Narrow <36607567+0pcom@users.noreply.github.com>
Date: Sun, 12 Mar 2023 12:23:00 -0500
Subject: [PATCH 03/19] small changes on skywire-cli mdisc

---
 cmd/skywire-cli/commands/mdisc/root.go       | 19 +++++++++++++++++--
 cmd/skywire-cli/commands/visor/transports.go |  4 +++-
 2 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/cmd/skywire-cli/commands/mdisc/root.go b/cmd/skywire-cli/commands/mdisc/root.go
index b630369df9..08b2158175 100644
--- a/cmd/skywire-cli/commands/mdisc/root.go
+++ b/cmd/skywire-cli/commands/mdisc/root.go
@@ -21,6 +21,7 @@ import (
 )
 
 var mdAddr string
+//var allEntries bool
 var masterLogger = logging.NewMasterLogger()
 var packageLogger = masterLogger.PackageLogger("mdisc:disc")
 
@@ -29,7 +30,8 @@ func init() {
 		entryCmd,
 		availableServersCmd,
 	)
-	entryCmd.PersistentFlags().StringVar(&mdAddr, "addr", utilenv.DmsgDiscAddr, "address of DMSG discovery server\n")
+	entryCmd.PersistentFlags().StringVarP(&mdAddr, "addr", "a", "", "DMSG discovery server address\n"+utilenv.DmsgDiscAddr)
+//	entryCmd.PersistentFlags().BoolVarP(&allEntries, "entries", "e", "", "get all entries")
 	availableServersCmd.PersistentFlags().StringVar(&mdAddr, "addr", utilenv.DmsgDiscAddr, "address of DMSG discovery server\n")
 	var helpflag bool
 	RootCmd.Flags().BoolVarP(&helpflag, "help", "h", false, "help for "+RootCmd.Use)
@@ -45,17 +47,30 @@ var RootCmd = &cobra.Command{
 var entryCmd = &cobra.Command{
 	Use:   "entry <visor-public-key>",
 	Short: "Fetch an entry",
-	Args:  cobra.MinimumNArgs(1),
+//	Args:  cobra.MinimumNArgs(1),
 	Run: func(cmd *cobra.Command, args []string) {
+		//print help on no args
+		if len(args) == 0 {
+			cmd.Help() //nolint
+		} else {
+			if mdAddr == "" {
+				mdAddr = utilenv.DmsgDiscAddr
+			}
 		ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
 		defer cancel()
 		pk := internal.ParsePK(cmd.Flags(), "visor-public-key", args[0])
 
 		masterLogger.SetLevel(logrus.InfoLevel)
 
+//TODO: fetch all entries
+//		if allEntries {
+//			entries, err := disc.NewHTTP(mdAddr, &http.Client{}, packageLogger).AvailableServers(ctx)
+//		}
+
 		entry, err := disc.NewHTTP(mdAddr, &http.Client{}, packageLogger).Entry(ctx, pk)
 		internal.Catch(cmd.Flags(), err)
 		internal.PrintOutput(cmd.Flags(), entry, fmt.Sprintln(entry))
+	}
 	},
 }
 
diff --git a/cmd/skywire-cli/commands/visor/transports.go b/cmd/skywire-cli/commands/visor/transports.go
index 5d1a37c30a..b1b25a9ae3 100644
--- a/cmd/skywire-cli/commands/visor/transports.go
+++ b/cmd/skywire-cli/commands/visor/transports.go
@@ -53,7 +53,9 @@ var tpCmd = &cobra.Command{
 	Each Transport is represented as a unique 16 byte (128 bit)
 	UUID value called the Transport ID
 	and has a Transport Type that identifies
-	a specific implementation of the Transport.`,
+	a specific implementation of the Transport.
+
+	Types: stcp stcpr sudph dmsg`,
 }
 
 var lsTypesCmd = &cobra.Command{

From 837b37307647b200cd97da8645b9d1cf85f802ef Mon Sep 17 00:00:00 2001
From: Moses Narrow <36607567+0pcom@users.noreply.github.com>
Date: Sun, 12 Mar 2023 12:53:29 -0500
Subject: [PATCH 04/19] add proxy list command, add related rpc methods

---
 cmd/skywire-cli/commands/mdisc/root.go  | 31 +++++------
 cmd/skywire-cli/commands/proxy/proxy.go | 69 +++++++++++++++++++++----
 cmd/skywire-cli/commands/proxy/root.go  |  9 ++++
 cmd/skywire-cli/commands/vpn/vvpn.go    | 27 +++++-----
 pkg/visor/api.go                        | 22 ++++++++
 pkg/visor/rpc.go                        | 16 ++++--
 pkg/visor/rpc_client.go                 | 17 +++++-
 7 files changed, 149 insertions(+), 42 deletions(-)

diff --git a/cmd/skywire-cli/commands/mdisc/root.go b/cmd/skywire-cli/commands/mdisc/root.go
index 08b2158175..4add3a9357 100644
--- a/cmd/skywire-cli/commands/mdisc/root.go
+++ b/cmd/skywire-cli/commands/mdisc/root.go
@@ -21,7 +21,8 @@ import (
 )
 
 var mdAddr string
-//var allEntries bool
+
+// var allEntries bool
 var masterLogger = logging.NewMasterLogger()
 var packageLogger = masterLogger.PackageLogger("mdisc:disc")
 
@@ -31,7 +32,7 @@ func init() {
 		availableServersCmd,
 	)
 	entryCmd.PersistentFlags().StringVarP(&mdAddr, "addr", "a", "", "DMSG discovery server address\n"+utilenv.DmsgDiscAddr)
-//	entryCmd.PersistentFlags().BoolVarP(&allEntries, "entries", "e", "", "get all entries")
+	//	entryCmd.PersistentFlags().BoolVarP(&allEntries, "entries", "e", "", "get all entries")
 	availableServersCmd.PersistentFlags().StringVar(&mdAddr, "addr", utilenv.DmsgDiscAddr, "address of DMSG discovery server\n")
 	var helpflag bool
 	RootCmd.Flags().BoolVarP(&helpflag, "help", "h", false, "help for "+RootCmd.Use)
@@ -47,7 +48,7 @@ var RootCmd = &cobra.Command{
 var entryCmd = &cobra.Command{
 	Use:   "entry <visor-public-key>",
 	Short: "Fetch an entry",
-//	Args:  cobra.MinimumNArgs(1),
+	//	Args:  cobra.MinimumNArgs(1),
 	Run: func(cmd *cobra.Command, args []string) {
 		//print help on no args
 		if len(args) == 0 {
@@ -56,21 +57,21 @@ var entryCmd = &cobra.Command{
 			if mdAddr == "" {
 				mdAddr = utilenv.DmsgDiscAddr
 			}
-		ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
-		defer cancel()
-		pk := internal.ParsePK(cmd.Flags(), "visor-public-key", args[0])
+			ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
+			defer cancel()
+			pk := internal.ParsePK(cmd.Flags(), "visor-public-key", args[0])
 
-		masterLogger.SetLevel(logrus.InfoLevel)
+			masterLogger.SetLevel(logrus.InfoLevel)
 
-//TODO: fetch all entries
-//		if allEntries {
-//			entries, err := disc.NewHTTP(mdAddr, &http.Client{}, packageLogger).AvailableServers(ctx)
-//		}
+			//TODO: fetch all entries
+			//		if allEntries {
+			//			entries, err := disc.NewHTTP(mdAddr, &http.Client{}, packageLogger).AvailableServers(ctx)
+			//		}
 
-		entry, err := disc.NewHTTP(mdAddr, &http.Client{}, packageLogger).Entry(ctx, pk)
-		internal.Catch(cmd.Flags(), err)
-		internal.PrintOutput(cmd.Flags(), entry, fmt.Sprintln(entry))
-	}
+			entry, err := disc.NewHTTP(mdAddr, &http.Client{}, packageLogger).Entry(ctx, pk)
+			internal.Catch(cmd.Flags(), err)
+			internal.PrintOutput(cmd.Flags(), entry, fmt.Sprintln(entry))
+		}
 	},
 }
 
diff --git a/cmd/skywire-cli/commands/proxy/proxy.go b/cmd/skywire-cli/commands/proxy/proxy.go
index 5a35d034f3..34cac80295 100644
--- a/cmd/skywire-cli/commands/proxy/proxy.go
+++ b/cmd/skywire-cli/commands/proxy/proxy.go
@@ -5,11 +5,13 @@ import (
 	"bytes"
 	"fmt"
 	"os"
+	"strings"
 	"text/tabwriter"
 	"time"
 
 	"github.com/spf13/cobra"
 
+	"github.com/skycoin/skywire-utilities/pkg/buildinfo"
 	clirpc "github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc"
 	"github.com/skycoin/skywire/cmd/skywire-cli/internal"
 	"github.com/skycoin/skywire/pkg/app/appserver"
@@ -20,16 +22,25 @@ var pk string
 func init() {
 	RootCmd.PersistentFlags().StringVar(&clirpc.Addr, "rpc", "localhost:3435", "RPC server address")
 	RootCmd.AddCommand(
-		skysockscStartCmd,
-		skysockscStopCmd,
-		skysockscStatusCmd,
+		proxyStartCmd,
+		proxyStopCmd,
+		proxyStatusCmd,
+		proxyListCmd,
 	)
-	skysockscStartCmd.Flags().StringVar(&pk, "pk", "", "skysocks server public key")
+	version := buildinfo.Version()
+	if version == "unknown" {
+		version = ""
+	}
+	proxyStartCmd.Flags().StringVar(&pk, "pk", "", "skysocks server public key")
+	proxyListCmd.Flags().BoolVarP(&isUnFiltered, "nofilter", "n", false, "provide unfiltered results")
+	proxyListCmd.Flags().StringVarP(&ver, "ver", "v", version, "filter results by version")
+	proxyListCmd.Flags().StringVarP(&country, "country", "c", "", "filter results by country")
+	proxyListCmd.Flags().BoolVarP(&isStats, "stats", "s", false, "return only a count of the results")
 }
 
-var skysockscStartCmd = &cobra.Command{
+var proxyStartCmd = &cobra.Command{
 	Use:   "start",
-	Short: "start the skysocks-client",
+	Short: "start the proxy client",
 	Args:  cobra.MinimumNArgs(0),
 	Run: func(cmd *cobra.Command, args []string) {
 		rpcClient, err := clirpc.Client(cmd.Flags())
@@ -68,9 +79,9 @@ var skysockscStartCmd = &cobra.Command{
 	},
 }
 
-var skysockscStopCmd = &cobra.Command{
+var proxyStopCmd = &cobra.Command{
 	Use:   "stop",
-	Short: "stop the skysocks-client",
+	Short: "stop the proxy client",
 	Run: func(cmd *cobra.Command, _ []string) {
 		rpcClient, err := clirpc.Client(cmd.Flags())
 		if err != nil {
@@ -81,9 +92,9 @@ var skysockscStopCmd = &cobra.Command{
 	},
 }
 
-var skysockscStatusCmd = &cobra.Command{
+var proxyStatusCmd = &cobra.Command{
 	Use:   "status",
-	Short: "skysocks-client status",
+	Short: "proxy client status",
 	Run: func(cmd *cobra.Command, _ []string) {
 		rpcClient, err := clirpc.Client(cmd.Flags())
 		if err != nil {
@@ -120,3 +131,41 @@ var skysockscStatusCmd = &cobra.Command{
 		internal.PrintOutput(cmd.Flags(), jsonAppStatus, b.String())
 	},
 }
+
+var proxyListCmd = &cobra.Command{
+	Use:   "list",
+	Short: "List servers",
+	Run: func(cmd *cobra.Command, _ []string) {
+		rpcClient, err := clirpc.Client(cmd.Flags())
+		if err != nil {
+			internal.PrintFatalRPCError(cmd.Flags(), err)
+		}
+		if isUnFiltered {
+			ver = ""
+			country = ""
+		}
+		servers, err := rpcClient.ProxyServers(ver, country)
+		if err != nil {
+			internal.PrintFatalRPCError(cmd.Flags(), err)
+		}
+		if len(servers) == 0 {
+			internal.PrintFatalError(cmd.Flags(), fmt.Errorf("No Servers found"))
+		}
+		if isStats {
+			internal.PrintOutput(cmd.Flags(), fmt.Sprintf("%d Servers\n", len(servers)), fmt.Sprintf("%d Servers\n", len(servers)))
+		} else {
+
+			var msg string
+			for _, i := range servers {
+				msg += strings.Replace(i.Addr.String(), ":44", "", 1)
+				if i.Geo != nil {
+					msg += fmt.Sprintf(" | %s\n", i.Geo.Country)
+				} else {
+					msg += "\n"
+				}
+			}
+
+			internal.PrintOutput(cmd.Flags(), servers, msg)
+		}
+	},
+}
diff --git a/cmd/skywire-cli/commands/proxy/root.go b/cmd/skywire-cli/commands/proxy/root.go
index e981153cf9..4de798dc23 100644
--- a/cmd/skywire-cli/commands/proxy/root.go
+++ b/cmd/skywire-cli/commands/proxy/root.go
@@ -5,6 +5,15 @@ import (
 	"github.com/spf13/cobra"
 )
 
+var (
+	path         string
+	isPkg        bool
+	isUnFiltered bool
+	ver          string
+	country      string
+	isStats      bool
+)
+
 // RootCmd contains commands that interact with the skywire-visor
 var RootCmd = &cobra.Command{
 	Use:   "proxy",
diff --git a/cmd/skywire-cli/commands/vpn/vvpn.go b/cmd/skywire-cli/commands/vpn/vvpn.go
index ce3e662168..177155070d 100644
--- a/cmd/skywire-cli/commands/vpn/vvpn.go
+++ b/cmd/skywire-cli/commands/vpn/vvpn.go
@@ -115,7 +115,7 @@ var vpnURLCmd = &cobra.Command{
 
 var vpnListCmd = &cobra.Command{
 	Use:   "list",
-	Short: "List public VPN servers",
+	Short: "List servers",
 	Run: func(cmd *cobra.Command, _ []string) {
 		rpcClient, err := clirpc.Client(cmd.Flags())
 		if err != nil {
@@ -130,23 +130,24 @@ var vpnListCmd = &cobra.Command{
 			internal.PrintFatalRPCError(cmd.Flags(), err)
 		}
 		if len(servers) == 0 {
-			internal.PrintFatalError(cmd.Flags(), fmt.Errorf("No VPN Servers found"))
+			internal.PrintFatalError(cmd.Flags(), fmt.Errorf("No Servers found"))
 		}
 		if isStats {
-			internal.PrintFatalError(cmd.Flags(), fmt.Errorf("%d VPN Servers", len(servers)))
-		}
+			internal.PrintOutput(cmd.Flags(), fmt.Sprintf("%d Servers\n", len(servers)), fmt.Sprintf("%d Servers\n", len(servers)))
+		} else {
 
-		var msg string
-		for _, i := range servers {
-			msg += strings.Replace(i.Addr.String(), ":44", "", 1)
-			if i.Geo != nil {
-				msg += fmt.Sprintf(" | %s\n", i.Geo.Country)
-			} else {
-				msg += "\n"
+			var msg string
+			for _, i := range servers {
+				msg += strings.Replace(i.Addr.String(), ":44", "", 1)
+				if i.Geo != nil {
+					msg += fmt.Sprintf(" | %s\n", i.Geo.Country)
+				} else {
+					msg += "\n"
+				}
 			}
-		}
 
-		internal.PrintOutput(cmd.Flags(), servers, msg)
+			internal.PrintOutput(cmd.Flags(), servers, msg)
+		}
 	},
 }
 
diff --git a/pkg/visor/api.go b/pkg/visor/api.go
index 95fa3bb4cc..03f812ebb8 100644
--- a/pkg/visor/api.go
+++ b/pkg/visor/api.go
@@ -89,6 +89,7 @@ type API interface {
 	//skysocks-client controls
 	StartSkysocksClient(pk string) error
 	StopSkysocksClient() error
+	ProxyServers(version, country string) ([]servicedisc.Service, error)
 
 	//transports
 	TransportTypes() ([]string, error)
@@ -916,6 +917,27 @@ func (v *Visor) VPNServers(version, country string) ([]servicedisc.Service, erro
 	return vpnServers, nil
 }
 
+// ProxyServers gets available public VPN server from service discovery URL
+func (v *Visor) ProxyServers(version, country string) ([]servicedisc.Service, error) {
+	log := logging.MustGetLogger("proxyservers")
+	vLog := logging.NewMasterLogger()
+	vLog.SetLevel(logrus.InfoLevel)
+
+	sdClient := servicedisc.NewClient(log, vLog, servicedisc.Config{
+		Type:          servicedisc.ServiceTypeProxy,
+		PK:            v.conf.PK,
+		SK:            v.conf.SK,
+		DiscAddr:      v.conf.Launcher.ServiceDisc,
+		DisplayNodeIP: v.conf.Launcher.DisplayNodeIP,
+	}, &http.Client{Timeout: time.Duration(20) * time.Second}, "")
+	proxyServers, err := sdClient.Services(context.Background(), 0, version, country)
+	if err != nil {
+		v.log.Error("Error getting public vpn servers: ", err)
+		return nil, err
+	}
+	return proxyServers, nil
+}
+
 // PublicVisors gets available public public visors from service discovery URL
 func (v *Visor) PublicVisors(version, country string) ([]servicedisc.Service, error) {
 	log := logging.MustGetLogger("public_visors")
diff --git a/pkg/visor/rpc.go b/pkg/visor/rpc.go
index 5be8726979..e6d40e7c78 100644
--- a/pkg/visor/rpc.go
+++ b/pkg/visor/rpc.go
@@ -635,14 +635,14 @@ func (r *RPC) SetPublicAutoconnect(pAc *bool, _ *struct{}) (err error) {
 	return err
 }
 
-// FilterVPNServersIn is input for VPNServers
-type FilterVPNServersIn struct {
+// FilterServersIn is input for VPNServers and ProxyServers
+type FilterServersIn struct {
 	Version string
 	Country string
 }
 
 // VPNServers gets available public VPN server from service discovery URL
-func (r *RPC) VPNServers(vc *FilterVPNServersIn, out *[]servicedisc.Service) (err error) {
+func (r *RPC) VPNServers(vc *FilterServersIn, out *[]servicedisc.Service) (err error) {
 	defer rpcutil.LogCall(r.log, "VPNServers", nil)(out, &err)
 	vpnServers, err := r.visor.VPNServers(vc.Version, vc.Country)
 	if vpnServers != nil {
@@ -651,6 +651,16 @@ func (r *RPC) VPNServers(vc *FilterVPNServersIn, out *[]servicedisc.Service) (er
 	return err
 }
 
+// VPNServers gets available public VPN server from service discovery URL
+func (r *RPC) ProxyServers(vc *FilterServersIn, out *[]servicedisc.Service) (err error) {
+	defer rpcutil.LogCall(r.log, "ProxyServers", nil)(out, &err)
+	proxyServers, err := r.visor.ProxyServers(vc.Version, vc.Country)
+	if proxyServers != nil {
+		*out = proxyServers
+	}
+	return err
+}
+
 // RemoteVisors return connected remote visors
 func (r *RPC) RemoteVisors(_ *struct{}, out *[]string) (err error) {
 	defer rpcutil.LogCall(r.log, "RemoteVisor", nil)(out, &err)
diff --git a/pkg/visor/rpc_client.go b/pkg/visor/rpc_client.go
index 92771da9ec..2c0f5c170f 100644
--- a/pkg/visor/rpc_client.go
+++ b/pkg/visor/rpc_client.go
@@ -489,7 +489,17 @@ type StatusMessage struct {
 // VPNServers calls VPNServers.
 func (rc *rpcClient) VPNServers(version, country string) ([]servicedisc.Service, error) {
 	output := []servicedisc.Service{}
-	err := rc.Call("VPNServers", &FilterVPNServersIn{ // nolint
+	err := rc.Call("VPNServers", &FilterServersIn{ // nolint
+		Version: version,
+		Country: country,
+	}, &output)
+	return output, err
+}
+
+// ProxyServers calls ProxyServers.
+func (rc *rpcClient) ProxyServers(version, country string) ([]servicedisc.Service, error) {
+	output := []servicedisc.Service{}
+	err := rc.Call("ProxyServers", &FilterServersIn{ // nolint
 		Version: version,
 		Country: country,
 	}, &output)
@@ -1219,6 +1229,11 @@ func (mc *mockRPCClient) VPNServers(_, _ string) ([]servicedisc.Service, error)
 	return []servicedisc.Service{}, nil
 }
 
+// ProxyServers implements API
+func (mc *mockRPCClient) ProxyServers(_, _ string) ([]servicedisc.Service, error) {
+	return []servicedisc.Service{}, nil
+}
+
 // RemoteVisors implements API
 func (mc *mockRPCClient) RemoteVisors() ([]string, error) {
 	return []string{}, nil

From 2dda39eec71e6221680006536f51a4e568ff8196 Mon Sep 17 00:00:00 2001
From: Moses Narrow <36607567+0pcom@users.noreply.github.com>
Date: Sun, 12 Mar 2023 12:55:42 -0500
Subject: [PATCH 05/19] small changes to proxy subcommands

---
 cmd/skywire-cli/commands/proxy/proxy.go | 2 +-
 cmd/skywire-cli/commands/vpn/vvpn.go    | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/cmd/skywire-cli/commands/proxy/proxy.go b/cmd/skywire-cli/commands/proxy/proxy.go
index 34cac80295..5cd3cc99bf 100644
--- a/cmd/skywire-cli/commands/proxy/proxy.go
+++ b/cmd/skywire-cli/commands/proxy/proxy.go
@@ -94,7 +94,7 @@ var proxyStopCmd = &cobra.Command{
 
 var proxyStatusCmd = &cobra.Command{
 	Use:   "status",
-	Short: "proxy client status",
+	Short: "proxy client(s) status",
 	Run: func(cmd *cobra.Command, _ []string) {
 		rpcClient, err := clirpc.Client(cmd.Flags())
 		if err != nil {
diff --git a/cmd/skywire-cli/commands/vpn/vvpn.go b/cmd/skywire-cli/commands/vpn/vvpn.go
index 177155070d..dfe0c78358 100644
--- a/cmd/skywire-cli/commands/vpn/vvpn.go
+++ b/cmd/skywire-cli/commands/vpn/vvpn.go
@@ -25,12 +25,12 @@ import (
 func init() {
 	RootCmd.PersistentFlags().StringVar(&clirpc.Addr, "rpc", "localhost:3435", "RPC server address")
 	RootCmd.AddCommand(
-		vpnListCmd,
 		vpnUICmd,
 		vpnURLCmd,
 		vpnStartCmd,
 		vpnStopCmd,
 		vpnStatusCmd,
+		vpnListCmd,
 	)
 	version := buildinfo.Version()
 	if version == "unknown" {

From f72f89c0b998392c19b8965ba4c09fd1a63efaa9 Mon Sep 17 00:00:00 2001
From: Moses Narrow <36607567+0pcom@users.noreply.github.com>
Date: Sun, 12 Mar 2023 13:03:13 -0500
Subject: [PATCH 06/19] add skywire-cli ut subcommand to query uptime tracker

---
 cmd/skywire-cli/commands/root.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/cmd/skywire-cli/commands/root.go b/cmd/skywire-cli/commands/root.go
index dae3699456..716ca4ff61 100644
--- a/cmd/skywire-cli/commands/root.go
+++ b/cmd/skywire-cli/commands/root.go
@@ -24,6 +24,7 @@ import (
 	cliskyfwd "github.com/skycoin/skywire/cmd/skywire-cli/commands/skyfwd"
 	cliskyrev "github.com/skycoin/skywire/cmd/skywire-cli/commands/skyrev"
 	clisurvey "github.com/skycoin/skywire/cmd/skywire-cli/commands/survey"
+	cliut "github.com/skycoin/skywire/cmd/skywire-cli/commands/ut"
 	clivisor "github.com/skycoin/skywire/cmd/skywire-cli/commands/visor"
 	clivpn "github.com/skycoin/skywire/cmd/skywire-cli/commands/vpn"
 	"github.com/skycoin/skywire/cmd/skywire-cli/internal"
@@ -181,6 +182,7 @@ func init() {
 		clidmsgpty.RootCmd,
 		clivisor.RootCmd,
 		clivpn.RootCmd,
+		cliut.RootCmd,
 		cliskyfwd.RootCmd,
 		cliskyrev.RootCmd,
 		clireward.RootCmd,

From 7d4f0791e5292cc0d39644f491ad7d14f8bd81b2 Mon Sep 17 00:00:00 2001
From: Moses Narrow <36607567+0pcom@users.noreply.github.com>
Date: Tue, 14 Mar 2023 21:58:28 -0500
Subject: [PATCH 07/19] add skyenv files and cmd/skywire-cli/commands/ut/

---
 cmd/skywire-cli/commands/ut/root.go | 94 +++++++++++++++++++++++++++++
 pkg/skyenv/skyenv_darwin.go         | 34 +++++++++++
 pkg/skyenv/skyenv_linux.go          | 31 ++++++++++
 pkg/skyenv/skyenv_windows.go        | 34 +++++++++++
 4 files changed, 193 insertions(+)
 create mode 100644 cmd/skywire-cli/commands/ut/root.go
 create mode 100644 pkg/skyenv/skyenv_darwin.go
 create mode 100644 pkg/skyenv/skyenv_linux.go
 create mode 100644 pkg/skyenv/skyenv_windows.go

diff --git a/cmd/skywire-cli/commands/ut/root.go b/cmd/skywire-cli/commands/ut/root.go
new file mode 100644
index 0000000000..925e8cebe0
--- /dev/null
+++ b/cmd/skywire-cli/commands/ut/root.go
@@ -0,0 +1,94 @@
+// Package cliut root.go
+
+package cliut
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"strconv"
+	"time"
+
+	"github.com/spf13/cobra"
+)
+
+var thisPk string
+
+const minUT float64 = 75.00
+
+// RootCmd contains commands that interact with the skywire-visor
+var RootCmd = &cobra.Command{
+	Use:   "ut",
+	Short: "query uptime tracker",
+	Run: func(cmd *cobra.Command, _ []string) {
+
+		now := time.Now()
+		url := "http://ut.skywire.skycoin.com/uptimes?v=v2"
+
+		utClient := http.Client{
+			Timeout: time.Second * 15, // Timeout after 2 seconds
+		}
+
+		req, err := http.NewRequest(http.MethodGet, url, nil)
+		if err != nil {
+			log.Fatal(err)
+		}
+
+		res, getErr := utClient.Do(req)
+		if getErr != nil {
+			log.Fatal(getErr)
+		}
+
+		if res.Body != nil {
+			defer res.Body.Close()
+		}
+
+		body, readErr := ioutil.ReadAll(res.Body)
+		if readErr != nil {
+			log.Fatal(readErr)
+		}
+		// startDate := time.Date(now.Year(), now.Month()-1, 1, 0, 0, 0, 0, now.Location()).Format("2006-01-02")
+		// endDate := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()).Add(-1 * time.Second).Format("2006-01-02")
+		startDate := time.Date(now.Year(), now.Month(), -1, 0, 0, 0, 0, now.Location()).Format("2006-01-02")
+		endDate := time.Date(now.Year(), now.Month()+1, 1, 0, 0, 0, 0, now.Location()).Add(-1 * time.Second).Format("2006-01-02")
+		uptimes := Uptimes{}
+		jsonErr := json.Unmarshal(body, &uptimes)
+		if jsonErr != nil {
+			log.Fatal(jsonErr)
+		}
+		for _, j := range uptimes {
+			thisPk = j.Pk
+			selectedDaily(j.Daily, startDate, endDate)
+		}
+
+	},
+}
+
+func selectedDaily(data map[string]string, startDate, endDate string) {
+	for date, uptime := range data {
+		if date >= startDate && date <= endDate {
+			utfloat, err := strconv.ParseFloat(uptime, 64)
+			if err != nil {
+				log.Fatal(err)
+			}
+			if utfloat >= minUT {
+				//        if date == startDate {
+				fmt.Printf(thisPk)
+				fmt.Printf(" ")
+				fmt.Println(date, uptime)
+				//        }
+			}
+		}
+	}
+}
+
+type Uptimes []struct {
+	Pk    string            `json:"pk"`
+	Up    int               `json:"up"`
+	Down  int               `json:"down"`
+	Pct   float64           `json:"pct"`
+	On    bool              `json:"on"`
+	Daily map[string]string `json:"daily,omitempty"`
+}
diff --git a/pkg/skyenv/skyenv_darwin.go b/pkg/skyenv/skyenv_darwin.go
new file mode 100644
index 0000000000..c5b050e1e5
--- /dev/null
+++ b/pkg/skyenv/skyenv_darwin.go
@@ -0,0 +1,34 @@
+//go:build linux
+// +build linux
+
+// 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"
+
+	//TODO: @mrpalide set this correctly for macos. it shouldn't be in the installed path
+
+	// SkyenvFilePath is the path to the SkyenvFile
+	SkyenvFilePath = "/Library/Application Support/Skywire"
+	// SkyenvFile contains environmental variables which are detected by `skywire-autoconfig` / `skywire-cli config auto` to set default or persistent values
+	SkyenvFile = "skyenv.sh"
+)
+
+// PackageConfig contains installation paths (for mac)
+func PackageConfig() PkgConfig {
+	pkgConfig := PkgConfig{
+		LauncherBinPath: "/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/pkg/skyenv/skyenv_linux.go b/pkg/skyenv/skyenv_linux.go
new file mode 100644
index 0000000000..a352aaeea6
--- /dev/null
+++ b/pkg/skyenv/skyenv_linux.go
@@ -0,0 +1,31 @@
+//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"
+	// SkyenvFilePath is the path to the SkyenvFile
+	SkyenvFilePath = "/etc/profile.d"
+	// SkyenvFile contains environmental variables which are detected by `skywire-autoconfig` / `skywire-cli config auto` to set default or persistent values
+	SkyenvFile = "skyenv.sh"
+)
+
+// PackageConfig contains installation paths (for linux)
+func PackageConfig() PkgConfig {
+	pkgConfig := PkgConfig{
+		LauncherBinPath: "/opt/skywire/apps",
+		LocalPath:       "/opt/skywire/local",
+		Hypervisor: Hypervisor{
+			DbPath:     "/opt/skywire/users.db",
+			EnableAuth: true,
+		},
+	}
+	return pkgConfig
+}
diff --git a/pkg/skyenv/skyenv_windows.go b/pkg/skyenv/skyenv_windows.go
new file mode 100644
index 0000000000..4d1689f41e
--- /dev/null
+++ b/pkg/skyenv/skyenv_windows.go
@@ -0,0 +1,34 @@
+//go:build linux
+// +build linux
+
+// 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"
+
+	//TODO: @mrpalide set this correctly for windows. it shouldn't be in the installed path
+
+	// SkyenvFilePath is the path to the SkyenvFile
+	SkyenvFilePath = "C:/Program Files/Skywire"
+	// SkyenvFile contains environmental variables which are detected by `skywire-autoconfig` / `skywire-cli config auto` to set default or persistent values
+	SkyenvFile = "skyenv.bat"
+)
+
+// PackageConfig contains installation paths (for windows)
+func PackageConfig() PkgConfig {
+	pkgConfig := PkgConfig{
+		LauncherBinPath: "C:/Program Files/Skywire/apps",
+		LocalPath:       "C:/Program Files/Skywire/local",
+		Hypervisor: Hypervisor{
+			DbPath:     "C:/Program Files/Skywire/users.db",
+			EnableAuth: true,
+		},
+	}
+	return pkgConfig
+}

From f6a14d5cc7d84184e69223e6e3b65d9839ba5c64 Mon Sep 17 00:00:00 2001
From: Moses Narrow <36607567+0pcom@users.noreply.github.com>
Date: Wed, 15 Mar 2023 12:26:19 -0500
Subject: [PATCH 08/19] improve skywire-cli proxy and skywire-cli vpn
 subcommands

---
 cmd/skywire-cli/commands/proxy/proxy.go |  93 ++++++++++---
 cmd/skywire-cli/commands/proxy/root.go  |   4 +
 cmd/skywire-cli/commands/vpn/root.go    |  11 ++
 cmd/skywire-cli/commands/vpn/vvpn.go    | 165 ++++++++++--------------
 cmd/skywire-cli/commands/vpn/vvpnui.go  |  99 ++++++++++++++
 5 files changed, 257 insertions(+), 115 deletions(-)
 create mode 100644 cmd/skywire-cli/commands/vpn/vvpnui.go

diff --git a/cmd/skywire-cli/commands/proxy/proxy.go b/cmd/skywire-cli/commands/proxy/proxy.go
index 5cd3cc99bf..028688b875 100644
--- a/cmd/skywire-cli/commands/proxy/proxy.go
+++ b/cmd/skywire-cli/commands/proxy/proxy.go
@@ -8,17 +8,16 @@ import (
 	"strings"
 	"text/tabwriter"
 	"time"
+	"math/rand"
+	"github.com/skycoin/skywire/pkg/servicedisc"
 
 	"github.com/spf13/cobra"
-
 	"github.com/skycoin/skywire-utilities/pkg/buildinfo"
 	clirpc "github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc"
 	"github.com/skycoin/skywire/cmd/skywire-cli/internal"
 	"github.com/skycoin/skywire/pkg/app/appserver"
 )
 
-var pk string
-
 func init() {
 	RootCmd.PersistentFlags().StringVar(&clirpc.Addr, "rpc", "localhost:3435", "RPC server address")
 	RootCmd.AddCommand(
@@ -31,8 +30,10 @@ func init() {
 	if version == "unknown" {
 		version = ""
 	}
-	proxyStartCmd.Flags().StringVar(&pk, "pk", "", "skysocks server public key")
-	proxyListCmd.Flags().BoolVarP(&isUnFiltered, "nofilter", "n", false, "provide unfiltered results")
+	proxyStartCmd.Flags().StringVarP(&pk, "pk", "k", "", "server public key")
+	proxyListCmd.Flags().StringVarP(&pk, "pk", "k", "", "check proxy service discovery for public key")
+	proxyListCmd.Flags().IntVarP(&count, "num", "n", 0, "number of results to return")
+	proxyListCmd.Flags().BoolVarP(&isUnFiltered, "unfilter", "u", false, "provide unfiltered results")
 	proxyListCmd.Flags().StringVarP(&ver, "ver", "v", version, "filter results by version")
 	proxyListCmd.Flags().StringVarP(&country, "country", "c", "", "filter results by country")
 	proxyListCmd.Flags().BoolVarP(&isStats, "stats", "s", false, "return only a count of the results")
@@ -41,13 +42,27 @@ func init() {
 var proxyStartCmd = &cobra.Command{
 	Use:   "start",
 	Short: "start the proxy client",
-	Args:  cobra.MinimumNArgs(0),
+//	Args:  cobra.MinimumNArgs(0),
 	Run: func(cmd *cobra.Command, args []string) {
+		//check that a valid public key is provided
+		err := pubkey.Set(pk)
+		if err != nil {
+			if len(args) > 0 {
+				err := pubkey.Set(args[0])
+				if err != nil {
+					internal.PrintFatalError(cmd.Flags(), err)
+				}
+			} else {
+				internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Invalid or missing public key"))
+			}
+		}
+		//connect to RPC
 		rpcClient, err := clirpc.Client(cmd.Flags())
 		if err != nil {
 			os.Exit(1)
 		}
-		internal.Catch(cmd.Flags(), rpcClient.StartSkysocksClient(pk))
+		//TODO: implement operational timeout
+		internal.Catch(cmd.Flags(), rpcClient.StartSkysocksClient(pubkey.String()))
 		internal.PrintOutput(cmd.Flags(), nil, "Starting.")
 		startProcess := true
 		for startProcess {
@@ -82,7 +97,7 @@ var proxyStartCmd = &cobra.Command{
 var proxyStopCmd = &cobra.Command{
 	Use:   "stop",
 	Short: "stop the proxy client",
-	Run: func(cmd *cobra.Command, _ []string) {
+	Run: func(cmd *cobra.Command, args []string) {
 		rpcClient, err := clirpc.Client(cmd.Flags())
 		if err != nil {
 			os.Exit(1)
@@ -95,7 +110,8 @@ var proxyStopCmd = &cobra.Command{
 var proxyStatusCmd = &cobra.Command{
 	Use:   "status",
 	Short: "proxy client(s) status",
-	Run: func(cmd *cobra.Command, _ []string) {
+	Run: func(cmd *cobra.Command, args []string) {
+		//TODO: check status of multiple clients
 		rpcClient, err := clirpc.Client(cmd.Flags())
 		if err != nil {
 			os.Exit(1)
@@ -135,7 +151,14 @@ var proxyStatusCmd = &cobra.Command{
 var proxyListCmd = &cobra.Command{
 	Use:   "list",
 	Short: "List servers",
-	Run: func(cmd *cobra.Command, _ []string) {
+	Long: "List proxy servers from service discovery\n http://sd.skycoin.com/api/services?type=proxy\n http://sd.skycoin.com/api/services?type=vpn&country=US",
+	Run: func(cmd *cobra.Command, args []string) {
+		if pk != "" {
+			err := pubkey.Set(pk)
+			if err != nil {
+				internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Invalid or missing public key"))
+			}
+		}
 		rpcClient, err := clirpc.Client(cmd.Flags())
 		if err != nil {
 			internal.PrintFatalRPCError(cmd.Flags(), err)
@@ -154,18 +177,48 @@ var proxyListCmd = &cobra.Command{
 		if isStats {
 			internal.PrintOutput(cmd.Flags(), fmt.Sprintf("%d Servers\n", len(servers)), fmt.Sprintf("%d Servers\n", len(servers)))
 		} else {
-
 			var msg string
-			for _, i := range servers {
-				msg += strings.Replace(i.Addr.String(), ":44", "", 1)
-				if i.Geo != nil {
-					msg += fmt.Sprintf(" | %s\n", i.Geo.Country)
-				} else {
-					msg += "\n"
-				}
-			}
-
+	    var results []string
+	    limit := len(servers)
+	    if count > 0 && count < limit {
+	        limit = count
+	    }
+	    if pk != "" {
+	        for _, server := range servers {
+	            if strings.Replace(server.Addr.String(), ":44", "", 1) == pk {
+	                results = append(results, server.Addr.String())
+	            }
+	        }
+	    } else {
+	        for _, server := range servers {
+	            results = append(results, server.Addr.String())
+	        }
+	    }
+	    rand.Shuffle(len(results), func(i, j int) {
+	        results[i], results[j] = results[j], results[i]
+	    })
+	    for i := 0; i < limit && i < len(results); i++ {
+	        msg += strings.Replace(results[i], ":44", "", 1)
+	        if server := findServerByPK(servers, results[i]); server != nil && server.Geo != nil {
+						if server.Geo.Country != "" {
+							msg += fmt.Sprintf(" | %s\n", server.Geo.Country)
+							} else {
+								msg += "\n"
+							}
+							} else {
+								msg += "\n"
+							}
+						}
 			internal.PrintOutput(cmd.Flags(), servers, msg)
 		}
 	},
 }
+
+func findServerByPK(servers []servicedisc.Service, addr string) *servicedisc.Service {
+    for _, server := range servers {
+        if server.Addr.String() == addr {
+            return &server
+        }
+    }
+    return nil
+}
diff --git a/cmd/skywire-cli/commands/proxy/root.go b/cmd/skywire-cli/commands/proxy/root.go
index 4de798dc23..a2d002e6a5 100644
--- a/cmd/skywire-cli/commands/proxy/root.go
+++ b/cmd/skywire-cli/commands/proxy/root.go
@@ -3,6 +3,7 @@ package skysocksc
 
 import (
 	"github.com/spf13/cobra"
+	"github.com/skycoin/skywire-utilities/pkg/cipher"
 )
 
 var (
@@ -12,6 +13,9 @@ var (
 	ver          string
 	country      string
 	isStats      bool
+	pubkey cipher.PubKey
+	pk string
+	count int
 )
 
 // RootCmd contains commands that interact with the skywire-visor
diff --git a/cmd/skywire-cli/commands/vpn/root.go b/cmd/skywire-cli/commands/vpn/root.go
index 2b3f460249..5f25734229 100644
--- a/cmd/skywire-cli/commands/vpn/root.go
+++ b/cmd/skywire-cli/commands/vpn/root.go
@@ -3,6 +3,10 @@ package clivpn
 
 import (
 	"github.com/spf13/cobra"
+	"github.com/skycoin/skywire-utilities/pkg/cipher"
+
+	clirpc "github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc"
+
 )
 
 var (
@@ -12,8 +16,15 @@ var (
 	ver          string
 	country      string
 	isStats      bool
+	pubkey cipher.PubKey
+	pk string
+	count int
 )
 
+func init() {
+	RootCmd.PersistentFlags().StringVar(&clirpc.Addr, "rpc", "localhost:3435", "RPC server address")
+}
+
 // RootCmd contains commands that interact with the skywire-visor
 var RootCmd = &cobra.Command{
 	Use:   "vpn",
diff --git a/cmd/skywire-cli/commands/vpn/vvpn.go b/cmd/skywire-cli/commands/vpn/vvpn.go
index dfe0c78358..d8740d8856 100644
--- a/cmd/skywire-cli/commands/vpn/vvpn.go
+++ b/cmd/skywire-cli/commands/vpn/vvpn.go
@@ -8,25 +8,20 @@ import (
 	"strings"
 	"text/tabwriter"
 	"time"
+	"math/rand"
 
 	"github.com/spf13/cobra"
-	"github.com/toqueteos/webbrowser"
+		"github.com/skycoin/skywire/pkg/servicedisc"
 
 	"github.com/skycoin/skywire-utilities/pkg/buildinfo"
-	"github.com/skycoin/skywire-utilities/pkg/cipher"
 	clirpc "github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc"
-	clivisor "github.com/skycoin/skywire/cmd/skywire-cli/commands/visor"
 	"github.com/skycoin/skywire/cmd/skywire-cli/internal"
 	"github.com/skycoin/skywire/pkg/app/appserver"
 	"github.com/skycoin/skywire/pkg/visor"
-	"github.com/skycoin/skywire/pkg/visor/visorconfig"
 )
 
 func init() {
-	RootCmd.PersistentFlags().StringVar(&clirpc.Addr, "rpc", "localhost:3435", "RPC server address")
 	RootCmd.AddCommand(
-		vpnUICmd,
-		vpnURLCmd,
 		vpnStartCmd,
 		vpnStopCmd,
 		vpnStatusCmd,
@@ -36,87 +31,25 @@ func init() {
 	if version == "unknown" {
 		version = ""
 	}
-	vpnUICmd.Flags().BoolVarP(&isPkg, "pkg", "p", false, "use package config path: "+visorconfig.SkywirePath)
-	vpnUICmd.Flags().StringVarP(&path, "config", "c", "", "config path")
-	vpnURLCmd.Flags().BoolVarP(&isPkg, "pkg", "p", false, "use package config path: "+visorconfig.SkywirePath)
-	vpnURLCmd.Flags().StringVarP(&path, "config", "c", "", "config path")
-	vpnListCmd.Flags().BoolVarP(&isUnFiltered, "nofilter", "n", false, "provide unfiltered results")
+	vpnStartCmd.Flags().StringVarP(&pk, "pk", "k", "", "server public key")
+	vpnListCmd.Flags().StringVarP(&pk, "pk", "k", "", "check proxy service discovery for public key")
+	vpnListCmd.Flags().IntVarP(&count, "num", "n", 0, "number of results to return")
+	vpnListCmd.Flags().BoolVarP(&isUnFiltered, "unfilter", "u", false, "provide unfiltered results")
 	vpnListCmd.Flags().StringVarP(&ver, "ver", "v", version, "filter results by version")
 	vpnListCmd.Flags().StringVarP(&country, "country", "c", "", "filter results by country")
 	vpnListCmd.Flags().BoolVarP(&isStats, "stats", "s", false, "return only a count of the results")
 }
 
-var vpnUICmd = &cobra.Command{
-	Use:   "ui",
-	Short: "Open VPN UI in default browser",
-	Run: func(cmd *cobra.Command, _ []string) {
-		var url string
-		if isPkg {
-			path = visorconfig.SkywireConfig()
-		}
-		if path != "" {
-			conf, err := visorconfig.ReadFile(path)
-			if err != nil {
-				internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Failed to read in config: %v", err))
-			}
-			url = fmt.Sprintf("http://127.0.0.1%s/#/vpn/%s/", clivisor.HypervisorPort(cmd.Flags()), conf.PK.Hex())
-		} else {
-			rpcClient, err := clirpc.Client(cmd.Flags())
-			if err != nil {
-				os.Exit(1)
-			}
-			overview, err := rpcClient.Overview()
-			if err != nil {
-				internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Failed to connect; is skywire running?: %v", err))
-			}
-			url = fmt.Sprintf("http://127.0.0.1%s/#/vpn/%s/", clivisor.HypervisorPort(cmd.Flags()), overview.PubKey.Hex())
-		}
-		if err := webbrowser.Open(url); err != nil {
-			internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Failed to open VPN UI in browser:: %v", err))
-		}
-	},
-}
-
-var vpnURLCmd = &cobra.Command{
-	Use:   "url",
-	Short: "Show VPN UI URL",
-	Run: func(cmd *cobra.Command, _ []string) {
-		var url string
-		if isPkg {
-			path = visorconfig.SkywireConfig()
-		}
-		if path != "" {
-			conf, err := visorconfig.ReadFile(path)
-			if err != nil {
-				internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Failed to read in config: %v", err))
-			}
-			url = fmt.Sprintf("http://127.0.0.1%s/#/vpn/%s/", clivisor.HypervisorPort(cmd.Flags()), conf.PK.Hex())
-		} else {
-			rpcClient, err := clirpc.Client(cmd.Flags())
-			if err != nil {
-				os.Exit(1)
-			}
-			overview, err := rpcClient.Overview()
-			if err != nil {
-				internal.PrintFatalRPCError(cmd.Flags(), err)
-			}
-			url = fmt.Sprintf("http://127.0.0.1%s/#/vpn/%s/", clivisor.HypervisorPort(cmd.Flags()), overview.PubKey.Hex())
-		}
-
-		output := struct {
-			URL string `json:"url"`
-		}{
-			URL: url,
-		}
-
-		internal.PrintOutput(cmd.Flags(), output, fmt.Sprintln(url))
-	},
-}
-
 var vpnListCmd = &cobra.Command{
 	Use:   "list",
 	Short: "List servers",
-	Run: func(cmd *cobra.Command, _ []string) {
+	Run: func(cmd *cobra.Command, args []string) {
+		if pk != "" {
+			err := pubkey.Set(pk)
+			if err != nil {
+				internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Invalid or missing public key"))
+			}
+		}
 		rpcClient, err := clirpc.Client(cmd.Flags())
 		if err != nil {
 			internal.PrintFatalRPCError(cmd.Flags(), err)
@@ -135,35 +68,77 @@ var vpnListCmd = &cobra.Command{
 		if isStats {
 			internal.PrintOutput(cmd.Flags(), fmt.Sprintf("%d Servers\n", len(servers)), fmt.Sprintf("%d Servers\n", len(servers)))
 		} else {
-
 			var msg string
-			for _, i := range servers {
-				msg += strings.Replace(i.Addr.String(), ":44", "", 1)
-				if i.Geo != nil {
-					msg += fmt.Sprintf(" | %s\n", i.Geo.Country)
-				} else {
-					msg += "\n"
-				}
-			}
-
+	    var results []string
+	    limit := len(servers)
+	    if count > 0 && count < limit {
+	        limit = count
+	    }
+	    if pk != "" {
+	        for _, server := range servers {
+	            if strings.Replace(server.Addr.String(), ":3", "", 1) == pk {
+	                results = append(results, server.Addr.String())
+	            }
+	        }
+	    } else {
+	        for _, server := range servers {
+	            results = append(results, server.Addr.String())
+	        }
+	    }
+	    rand.Shuffle(len(results), func(i, j int) {
+	        results[i], results[j] = results[j], results[i]
+	    })
+	    for i := 0; i < limit && i < len(results); i++ {
+	        msg += strings.Replace(results[i], ":3", "", 1)
+	        if server := findServerByPK(servers, results[i]); server != nil && server.Geo != nil {
+						if server.Geo.Country != "" {
+							msg += fmt.Sprintf(" | %s\n", server.Geo.Country)
+							} else {
+								msg += "\n"
+							}
+							} else {
+								msg += "\n"
+							}
+						}
 			internal.PrintOutput(cmd.Flags(), servers, msg)
 		}
 	},
 }
 
+func findServerByPK(servers []servicedisc.Service, addr string) *servicedisc.Service {
+    for _, server := range servers {
+        if server.Addr.String() == addr {
+            return &server
+        }
+    }
+    return nil
+}
+
+
 var vpnStartCmd = &cobra.Command{
 	Use:   "start <public-key>",
 	Short: "start the vpn for <public-key>",
-	Args:  cobra.MinimumNArgs(1),
+//	Args:  cobra.MinimumNArgs(1),
 	Run: func(cmd *cobra.Command, args []string) {
-
-		var pk cipher.PubKey
-		internal.Catch(cmd.Flags(), pk.Set(args[0]))
+		//check that a valid public key is provided
+		err := pubkey.Set(pk)
+		if err != nil {
+			if len(args) > 0 {
+				err := pubkey.Set(args[0])
+				if err != nil {
+					internal.PrintFatalError(cmd.Flags(), err)
+				}
+			} else {
+				internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Invalid or missing public key"))
+			}
+		}
+		//connect to RPC
+		internal.Catch(cmd.Flags(), pubkey.Set(args[0]))
 		rpcClient, err := clirpc.Client(cmd.Flags())
 		if err != nil {
 			os.Exit(1)
 		}
-		internal.Catch(cmd.Flags(), rpcClient.StartVPNClient(pk))
+		internal.Catch(cmd.Flags(), rpcClient.StartVPNClient(pubkey))
 		internal.PrintOutput(cmd.Flags(), nil, "Starting.")
 		startProcess := true
 		for startProcess {
diff --git a/cmd/skywire-cli/commands/vpn/vvpnui.go b/cmd/skywire-cli/commands/vpn/vvpnui.go
new file mode 100644
index 0000000000..a3542c9e76
--- /dev/null
+++ b/cmd/skywire-cli/commands/vpn/vvpnui.go
@@ -0,0 +1,99 @@
+// Package clivpn cmd/skywire-cli/commands/vpn/vvpn.go
+package clivpn
+
+import (
+	"fmt"
+	"os"
+
+	"github.com/spf13/cobra"
+	"github.com/toqueteos/webbrowser"
+
+	"github.com/skycoin/skywire-utilities/pkg/buildinfo"
+	clirpc "github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc"
+	clivisor "github.com/skycoin/skywire/cmd/skywire-cli/commands/visor"
+	"github.com/skycoin/skywire/cmd/skywire-cli/internal"
+
+	"github.com/skycoin/skywire/pkg/visor/visorconfig"
+)
+
+func init() {
+	RootCmd.AddCommand(
+		vpnUICmd,
+		vpnURLCmd,
+	)
+	version := buildinfo.Version()
+	if version == "unknown" {
+		version = ""
+	}
+	vpnUICmd.Flags().BoolVarP(&isPkg, "pkg", "p", false, "use package config path: "+visorconfig.SkywirePath)
+	vpnUICmd.Flags().StringVarP(&path, "config", "c", "", "config path")
+	vpnURLCmd.Flags().BoolVarP(&isPkg, "pkg", "p", false, "use package config path: "+visorconfig.SkywirePath)
+	vpnURLCmd.Flags().StringVarP(&path, "config", "c", "", "config path")
+}
+
+var vpnUICmd = &cobra.Command{
+	Use:   "ui",
+	Short: "Open VPN UI in default browser",
+	Run: func(cmd *cobra.Command, _ []string) {
+		var url string
+		if isPkg {
+			path = visorconfig.SkywireConfig()
+		}
+		if path != "" {
+			conf, err := visorconfig.ReadFile(path)
+			if err != nil {
+				internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Failed to read in config: %v", err))
+			}
+			url = fmt.Sprintf("http://127.0.0.1%s/#/vpn/%s/", clivisor.HypervisorPort(cmd.Flags()), conf.PK.Hex())
+		} else {
+			rpcClient, err := clirpc.Client(cmd.Flags())
+			if err != nil {
+				os.Exit(1)
+			}
+			overview, err := rpcClient.Overview()
+			if err != nil {
+				internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Failed to connect; is skywire running?: %v", err))
+			}
+			url = fmt.Sprintf("http://127.0.0.1%s/#/vpn/%s/", clivisor.HypervisorPort(cmd.Flags()), overview.PubKey.Hex())
+		}
+		if err := webbrowser.Open(url); err != nil {
+			internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Failed to open VPN UI in browser:: %v", err))
+		}
+	},
+}
+
+var vpnURLCmd = &cobra.Command{
+	Use:   "url",
+	Short: "Show VPN UI URL",
+	Run: func(cmd *cobra.Command, _ []string) {
+		var url string
+		if isPkg {
+			path = visorconfig.SkywireConfig()
+		}
+		if path != "" {
+			conf, err := visorconfig.ReadFile(path)
+			if err != nil {
+				internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Failed to read in config: %v", err))
+			}
+			url = fmt.Sprintf("http://127.0.0.1%s/#/vpn/%s/", clivisor.HypervisorPort(cmd.Flags()), conf.PK.Hex())
+		} else {
+			rpcClient, err := clirpc.Client(cmd.Flags())
+			if err != nil {
+				os.Exit(1)
+			}
+			overview, err := rpcClient.Overview()
+			if err != nil {
+				internal.PrintFatalRPCError(cmd.Flags(), err)
+			}
+			url = fmt.Sprintf("http://127.0.0.1%s/#/vpn/%s/", clivisor.HypervisorPort(cmd.Flags()), overview.PubKey.Hex())
+		}
+
+		output := struct {
+			URL string `json:"url"`
+		}{
+			URL: url,
+		}
+
+		internal.PrintOutput(cmd.Flags(), output, fmt.Sprintln(url))
+	},
+}

From 5b1c560786da337c19f0921ef81fef91fca535ff Mon Sep 17 00:00:00 2001
From: Moses Narrow <36607567+0pcom@users.noreply.github.com>
Date: Wed, 15 Mar 2023 12:53:36 -0500
Subject: [PATCH 09/19] improve skywire-cli ut subcommand with flags

---
 cmd/skywire-cli/commands/ut/root.go | 25 +++++++++++++++++++++----
 1 file changed, 21 insertions(+), 4 deletions(-)

diff --git a/cmd/skywire-cli/commands/ut/root.go b/cmd/skywire-cli/commands/ut/root.go
index 925e8cebe0..0314e1f731 100644
--- a/cmd/skywire-cli/commands/ut/root.go
+++ b/cmd/skywire-cli/commands/ut/root.go
@@ -10,23 +10,39 @@ import (
 	"net/http"
 	"strconv"
 	"time"
+	"github.com/skycoin/skywire-utilities/pkg/cipher"
+	"github.com/skycoin/skywire/cmd/skywire-cli/internal"
 
 	"github.com/spf13/cobra"
 )
 
+var pubkey cipher.PubKey
+var pk string
 var thisPk string
 
-const minUT float64 = 75.00
+var minUT int
+func init() {
+	RootCmd.Flags().StringVarP(&pk, "pk", "k", "", "check uptime for the specified key")
+	RootCmd.Flags().IntVarP(&minUT, "min", "n", 75, "list visors meeting minimum uptime")
+}
+
 
 // RootCmd contains commands that interact with the skywire-visor
 var RootCmd = &cobra.Command{
 	Use:   "ut",
 	Short: "query uptime tracker",
 	Run: func(cmd *cobra.Command, _ []string) {
-
-		now := time.Now()
 		url := "http://ut.skywire.skycoin.com/uptimes?v=v2"
 
+		now := time.Now()
+		if pk != "" {
+			err := pubkey.Set(pk)
+			if err != nil {
+					internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Invalid or missing public key"))
+			} else {
+				url = "http://ut.skywire.skycoin.com/uptimes?v=v2&visors="+pubkey.String()
+		}
+	}
 		utClient := http.Client{
 			Timeout: time.Second * 15, // Timeout after 2 seconds
 		}
@@ -49,6 +65,7 @@ var RootCmd = &cobra.Command{
 		if readErr != nil {
 			log.Fatal(readErr)
 		}
+
 		// startDate := time.Date(now.Year(), now.Month()-1, 1, 0, 0, 0, 0, now.Location()).Format("2006-01-02")
 		// endDate := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()).Add(-1 * time.Second).Format("2006-01-02")
 		startDate := time.Date(now.Year(), now.Month(), -1, 0, 0, 0, 0, now.Location()).Format("2006-01-02")
@@ -73,7 +90,7 @@ func selectedDaily(data map[string]string, startDate, endDate string) {
 			if err != nil {
 				log.Fatal(err)
 			}
-			if utfloat >= minUT {
+			if utfloat >= float64(minUT) {
 				//        if date == startDate {
 				fmt.Printf(thisPk)
 				fmt.Printf(" ")

From 807576d3444368cec042b6857dac40490d27b69a Mon Sep 17 00:00:00 2001
From: Moses Narrow <36607567+0pcom@users.noreply.github.com>
Date: Wed, 15 Mar 2023 13:37:22 -0500
Subject: [PATCH 10/19] improve skywire-cli ut subcommand. Check uptime for
 single PK, return a count of online visors

---
 cmd/skywire-cli/commands/ut/root.go | 29 ++++++++++++++++++++++++-----
 1 file changed, 24 insertions(+), 5 deletions(-)

diff --git a/cmd/skywire-cli/commands/ut/root.go b/cmd/skywire-cli/commands/ut/root.go
index 0314e1f731..80d4006855 100644
--- a/cmd/skywire-cli/commands/ut/root.go
+++ b/cmd/skywire-cli/commands/ut/root.go
@@ -10,6 +10,7 @@ import (
 	"net/http"
 	"strconv"
 	"time"
+	"os"
 	"github.com/skycoin/skywire-utilities/pkg/cipher"
 	"github.com/skycoin/skywire/cmd/skywire-cli/internal"
 
@@ -19,10 +20,15 @@ import (
 var pubkey cipher.PubKey
 var pk string
 var thisPk string
+var online bool
+var isStats      bool
+
 
 var minUT int
 func init() {
 	RootCmd.Flags().StringVarP(&pk, "pk", "k", "", "check uptime for the specified key")
+	RootCmd.Flags().BoolVarP(&online, "on", "o", false, "list currently online visors")
+	RootCmd.Flags().BoolVarP(&isStats, "stats", "s", false, "count the number of results")
 	RootCmd.Flags().IntVarP(&minUT, "min", "n", 75, "list visors meeting minimum uptime")
 }
 
@@ -33,7 +39,6 @@ var RootCmd = &cobra.Command{
 	Short: "query uptime tracker",
 	Run: func(cmd *cobra.Command, _ []string) {
 		url := "http://ut.skywire.skycoin.com/uptimes?v=v2"
-
 		now := time.Now()
 		if pk != "" {
 			err := pubkey.Set(pk)
@@ -66,8 +71,6 @@ var RootCmd = &cobra.Command{
 			log.Fatal(readErr)
 		}
 
-		// startDate := time.Date(now.Year(), now.Month()-1, 1, 0, 0, 0, 0, now.Location()).Format("2006-01-02")
-		// endDate := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()).Add(-1 * time.Second).Format("2006-01-02")
 		startDate := time.Date(now.Year(), now.Month(), -1, 0, 0, 0, 0, now.Location()).Format("2006-01-02")
 		endDate := time.Date(now.Year(), now.Month()+1, 1, 0, 0, 0, 0, now.Location()).Add(-1 * time.Second).Format("2006-01-02")
 		uptimes := Uptimes{}
@@ -75,11 +78,26 @@ var RootCmd = &cobra.Command{
 		if jsonErr != nil {
 			log.Fatal(jsonErr)
 		}
+		var msg []string
 		for _, j := range uptimes {
 			thisPk = j.Pk
-			selectedDaily(j.Daily, startDate, endDate)
+			if online {
+				if j.On {
+					msg = append(msg, fmt.Sprintf(thisPk+"\n"))
+				}
+			} else {
+				selectedDaily(j.Daily, startDate, endDate)
+			}
+		}
+		if online {
+			if isStats {
+				internal.PrintOutput(cmd.Flags(), fmt.Sprintf("%d visors online\n", len(msg)), fmt.Sprintf("%d visors online\n", len(msg)))
+				os.Exit(0)
+			}
+			for _, i := range msg {
+				internal.PrintOutput(cmd.Flags(), fmt.Sprintf("%s", i), fmt.Sprintf("%s", i))
+		}
 		}
-
 	},
 }
 
@@ -101,6 +119,7 @@ func selectedDaily(data map[string]string, startDate, endDate string) {
 	}
 }
 
+
 type Uptimes []struct {
 	Pk    string            `json:"pk"`
 	Up    int               `json:"up"`

From 0c384af9309252d2dbfc883a7e2812ccd4dc9fa4 Mon Sep 17 00:00:00 2001
From: Moses Narrow <36607567+0pcom@users.noreply.github.com>
Date: Thu, 23 Mar 2023 11:11:08 -0500
Subject: [PATCH 11/19] add flag to specify alternative uptime tracker to
 skywire-cli ut command

---
 cmd/skywire-cli/commands/proxy/proxy.go | 83 +++++++++++++------------
 cmd/skywire-cli/commands/proxy/root.go  |  7 ++-
 cmd/skywire-cli/commands/ut/root.go     | 41 ++++++------
 cmd/skywire-cli/commands/vpn/root.go    |  9 ++-
 cmd/skywire-cli/commands/vpn/vvpn.go    | 81 ++++++++++++------------
 cmd/skywire-cli/commands/vpn/vvpnui.go  |  1 -
 6 files changed, 112 insertions(+), 110 deletions(-)

diff --git a/cmd/skywire-cli/commands/proxy/proxy.go b/cmd/skywire-cli/commands/proxy/proxy.go
index 028688b875..df7b4484f6 100644
--- a/cmd/skywire-cli/commands/proxy/proxy.go
+++ b/cmd/skywire-cli/commands/proxy/proxy.go
@@ -4,18 +4,19 @@ package skysocksc
 import (
 	"bytes"
 	"fmt"
+	"math/rand"
 	"os"
 	"strings"
 	"text/tabwriter"
 	"time"
-	"math/rand"
-	"github.com/skycoin/skywire/pkg/servicedisc"
 
 	"github.com/spf13/cobra"
+
 	"github.com/skycoin/skywire-utilities/pkg/buildinfo"
 	clirpc "github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc"
 	"github.com/skycoin/skywire/cmd/skywire-cli/internal"
 	"github.com/skycoin/skywire/pkg/app/appserver"
+	"github.com/skycoin/skywire/pkg/servicedisc"
 )
 
 func init() {
@@ -42,7 +43,7 @@ func init() {
 var proxyStartCmd = &cobra.Command{
 	Use:   "start",
 	Short: "start the proxy client",
-//	Args:  cobra.MinimumNArgs(0),
+	//	Args:  cobra.MinimumNArgs(0),
 	Run: func(cmd *cobra.Command, args []string) {
 		//check that a valid public key is provided
 		err := pubkey.Set(pk)
@@ -151,7 +152,7 @@ var proxyStatusCmd = &cobra.Command{
 var proxyListCmd = &cobra.Command{
 	Use:   "list",
 	Short: "List servers",
-	Long: "List proxy servers from service discovery\n http://sd.skycoin.com/api/services?type=proxy\n http://sd.skycoin.com/api/services?type=vpn&country=US",
+	Long:  "List proxy servers from service discovery\n http://sd.skycoin.com/api/services?type=proxy\n http://sd.skycoin.com/api/services?type=vpn&country=US",
 	Run: func(cmd *cobra.Command, args []string) {
 		if pk != "" {
 			err := pubkey.Set(pk)
@@ -178,47 +179,47 @@ var proxyListCmd = &cobra.Command{
 			internal.PrintOutput(cmd.Flags(), fmt.Sprintf("%d Servers\n", len(servers)), fmt.Sprintf("%d Servers\n", len(servers)))
 		} else {
 			var msg string
-	    var results []string
-	    limit := len(servers)
-	    if count > 0 && count < limit {
-	        limit = count
-	    }
-	    if pk != "" {
-	        for _, server := range servers {
-	            if strings.Replace(server.Addr.String(), ":44", "", 1) == pk {
-	                results = append(results, server.Addr.String())
-	            }
-	        }
-	    } else {
-	        for _, server := range servers {
-	            results = append(results, server.Addr.String())
-	        }
-	    }
-	    rand.Shuffle(len(results), func(i, j int) {
-	        results[i], results[j] = results[j], results[i]
-	    })
-	    for i := 0; i < limit && i < len(results); i++ {
-	        msg += strings.Replace(results[i], ":44", "", 1)
-	        if server := findServerByPK(servers, results[i]); server != nil && server.Geo != nil {
-						if server.Geo.Country != "" {
-							msg += fmt.Sprintf(" | %s\n", server.Geo.Country)
-							} else {
-								msg += "\n"
-							}
-							} else {
-								msg += "\n"
-							}
-						}
+			var results []string
+			limit := len(servers)
+			if count > 0 && count < limit {
+				limit = count
+			}
+			if pk != "" {
+				for _, server := range servers {
+					if strings.Replace(server.Addr.String(), ":44", "", 1) == pk {
+						results = append(results, server.Addr.String())
+					}
+				}
+			} else {
+				for _, server := range servers {
+					results = append(results, server.Addr.String())
+				}
+			}
+			rand.Shuffle(len(results), func(i, j int) {
+				results[i], results[j] = results[j], results[i]
+			})
+			for i := 0; i < limit && i < len(results); i++ {
+				msg += strings.Replace(results[i], ":44", "", 1)
+				if server := findServerByPK(servers, results[i]); server != nil && server.Geo != nil {
+					if server.Geo.Country != "" {
+						msg += fmt.Sprintf(" | %s\n", server.Geo.Country)
+					} else {
+						msg += "\n"
+					}
+				} else {
+					msg += "\n"
+				}
+			}
 			internal.PrintOutput(cmd.Flags(), servers, msg)
 		}
 	},
 }
 
 func findServerByPK(servers []servicedisc.Service, addr string) *servicedisc.Service {
-    for _, server := range servers {
-        if server.Addr.String() == addr {
-            return &server
-        }
-    }
-    return nil
+	for _, server := range servers {
+		if server.Addr.String() == addr {
+			return &server
+		}
+	}
+	return nil
 }
diff --git a/cmd/skywire-cli/commands/proxy/root.go b/cmd/skywire-cli/commands/proxy/root.go
index a2d002e6a5..23f5bd7c7d 100644
--- a/cmd/skywire-cli/commands/proxy/root.go
+++ b/cmd/skywire-cli/commands/proxy/root.go
@@ -3,6 +3,7 @@ package skysocksc
 
 import (
 	"github.com/spf13/cobra"
+
 	"github.com/skycoin/skywire-utilities/pkg/cipher"
 )
 
@@ -13,9 +14,9 @@ var (
 	ver          string
 	country      string
 	isStats      bool
-	pubkey cipher.PubKey
-	pk string
-	count int
+	pubkey       cipher.PubKey
+	pk           string
+	count        int
 )
 
 // RootCmd contains commands that interact with the skywire-visor
diff --git a/cmd/skywire-cli/commands/ut/root.go b/cmd/skywire-cli/commands/ut/root.go
index 80d4006855..f94265ac87 100644
--- a/cmd/skywire-cli/commands/ut/root.go
+++ b/cmd/skywire-cli/commands/ut/root.go
@@ -8,48 +8,54 @@ import (
 	"io/ioutil"
 	"log"
 	"net/http"
+	"os"
 	"strconv"
 	"time"
-	"os"
-	"github.com/skycoin/skywire-utilities/pkg/cipher"
-	"github.com/skycoin/skywire/cmd/skywire-cli/internal"
 
 	"github.com/spf13/cobra"
-)
 
-var pubkey cipher.PubKey
-var pk string
-var thisPk string
-var online bool
-var isStats      bool
+	"github.com/skycoin/skywire-utilities/pkg/cipher"
+	"github.com/skycoin/skywire/cmd/skywire-cli/internal"
+)
 
+var (
+	pubkey  cipher.PubKey
+	pk      string
+	thisPk  string
+	online  bool
+	isStats bool
+	url     string
+)
 
 var minUT int
+
 func init() {
 	RootCmd.Flags().StringVarP(&pk, "pk", "k", "", "check uptime for the specified key")
 	RootCmd.Flags().BoolVarP(&online, "on", "o", false, "list currently online visors")
 	RootCmd.Flags().BoolVarP(&isStats, "stats", "s", false, "count the number of results")
 	RootCmd.Flags().IntVarP(&minUT, "min", "n", 75, "list visors meeting minimum uptime")
+	RootCmd.Flags().StringVarP(&url, "url", "u", "", "specify alternative uptime tracker url\ndefault: http://ut.skywire.skycoin.com/uptimes?v=v2")
 }
 
-
 // RootCmd contains commands that interact with the skywire-visor
 var RootCmd = &cobra.Command{
 	Use:   "ut",
 	Short: "query uptime tracker",
 	Run: func(cmd *cobra.Command, _ []string) {
-		url := "http://ut.skywire.skycoin.com/uptimes?v=v2"
+		if url == "" {
+			url = "http://ut.skywire.skycoin.com/uptimes?v=v2"
+		}
 		now := time.Now()
 		if pk != "" {
 			err := pubkey.Set(pk)
 			if err != nil {
-					internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Invalid or missing public key"))
+				internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Invalid or missing public key"))
 			} else {
-				url = "http://ut.skywire.skycoin.com/uptimes?v=v2&visors="+pubkey.String()
+				url += url + "&visors=" + pubkey.String()
+			}
 		}
-	}
 		utClient := http.Client{
-			Timeout: time.Second * 15, // Timeout after 2 seconds
+			Timeout: time.Second * 15, // Timeout after 15 seconds
 		}
 
 		req, err := http.NewRequest(http.MethodGet, url, nil)
@@ -96,7 +102,7 @@ var RootCmd = &cobra.Command{
 			}
 			for _, i := range msg {
 				internal.PrintOutput(cmd.Flags(), fmt.Sprintf("%s", i), fmt.Sprintf("%s", i))
-		}
+			}
 		}
 	},
 }
@@ -109,17 +115,14 @@ func selectedDaily(data map[string]string, startDate, endDate string) {
 				log.Fatal(err)
 			}
 			if utfloat >= float64(minUT) {
-				//        if date == startDate {
 				fmt.Printf(thisPk)
 				fmt.Printf(" ")
 				fmt.Println(date, uptime)
-				//        }
 			}
 		}
 	}
 }
 
-
 type Uptimes []struct {
 	Pk    string            `json:"pk"`
 	Up    int               `json:"up"`
diff --git a/cmd/skywire-cli/commands/vpn/root.go b/cmd/skywire-cli/commands/vpn/root.go
index 5f25734229..85dd497bc7 100644
--- a/cmd/skywire-cli/commands/vpn/root.go
+++ b/cmd/skywire-cli/commands/vpn/root.go
@@ -3,10 +3,9 @@ package clivpn
 
 import (
 	"github.com/spf13/cobra"
-	"github.com/skycoin/skywire-utilities/pkg/cipher"
 
+	"github.com/skycoin/skywire-utilities/pkg/cipher"
 	clirpc "github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc"
-
 )
 
 var (
@@ -16,9 +15,9 @@ var (
 	ver          string
 	country      string
 	isStats      bool
-	pubkey cipher.PubKey
-	pk string
-	count int
+	pubkey       cipher.PubKey
+	pk           string
+	count        int
 )
 
 func init() {
diff --git a/cmd/skywire-cli/commands/vpn/vvpn.go b/cmd/skywire-cli/commands/vpn/vvpn.go
index d8740d8856..dd9012dc9b 100644
--- a/cmd/skywire-cli/commands/vpn/vvpn.go
+++ b/cmd/skywire-cli/commands/vpn/vvpn.go
@@ -4,19 +4,19 @@ package clivpn
 import (
 	"bytes"
 	"fmt"
+	"math/rand"
 	"os"
 	"strings"
 	"text/tabwriter"
 	"time"
-	"math/rand"
 
 	"github.com/spf13/cobra"
-		"github.com/skycoin/skywire/pkg/servicedisc"
 
 	"github.com/skycoin/skywire-utilities/pkg/buildinfo"
 	clirpc "github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc"
 	"github.com/skycoin/skywire/cmd/skywire-cli/internal"
 	"github.com/skycoin/skywire/pkg/app/appserver"
+	"github.com/skycoin/skywire/pkg/servicedisc"
 	"github.com/skycoin/skywire/pkg/visor"
 )
 
@@ -69,56 +69,55 @@ var vpnListCmd = &cobra.Command{
 			internal.PrintOutput(cmd.Flags(), fmt.Sprintf("%d Servers\n", len(servers)), fmt.Sprintf("%d Servers\n", len(servers)))
 		} else {
 			var msg string
-	    var results []string
-	    limit := len(servers)
-	    if count > 0 && count < limit {
-	        limit = count
-	    }
-	    if pk != "" {
-	        for _, server := range servers {
-	            if strings.Replace(server.Addr.String(), ":3", "", 1) == pk {
-	                results = append(results, server.Addr.String())
-	            }
-	        }
-	    } else {
-	        for _, server := range servers {
-	            results = append(results, server.Addr.String())
-	        }
-	    }
-	    rand.Shuffle(len(results), func(i, j int) {
-	        results[i], results[j] = results[j], results[i]
-	    })
-	    for i := 0; i < limit && i < len(results); i++ {
-	        msg += strings.Replace(results[i], ":3", "", 1)
-	        if server := findServerByPK(servers, results[i]); server != nil && server.Geo != nil {
-						if server.Geo.Country != "" {
-							msg += fmt.Sprintf(" | %s\n", server.Geo.Country)
-							} else {
-								msg += "\n"
-							}
-							} else {
-								msg += "\n"
-							}
-						}
+			var results []string
+			limit := len(servers)
+			if count > 0 && count < limit {
+				limit = count
+			}
+			if pk != "" {
+				for _, server := range servers {
+					if strings.Replace(server.Addr.String(), ":3", "", 1) == pk {
+						results = append(results, server.Addr.String())
+					}
+				}
+			} else {
+				for _, server := range servers {
+					results = append(results, server.Addr.String())
+				}
+			}
+			rand.Shuffle(len(results), func(i, j int) {
+				results[i], results[j] = results[j], results[i]
+			})
+			for i := 0; i < limit && i < len(results); i++ {
+				msg += strings.Replace(results[i], ":3", "", 1)
+				if server := findServerByPK(servers, results[i]); server != nil && server.Geo != nil {
+					if server.Geo.Country != "" {
+						msg += fmt.Sprintf(" | %s\n", server.Geo.Country)
+					} else {
+						msg += "\n"
+					}
+				} else {
+					msg += "\n"
+				}
+			}
 			internal.PrintOutput(cmd.Flags(), servers, msg)
 		}
 	},
 }
 
 func findServerByPK(servers []servicedisc.Service, addr string) *servicedisc.Service {
-    for _, server := range servers {
-        if server.Addr.String() == addr {
-            return &server
-        }
-    }
-    return nil
+	for _, server := range servers {
+		if server.Addr.String() == addr {
+			return &server
+		}
+	}
+	return nil
 }
 
-
 var vpnStartCmd = &cobra.Command{
 	Use:   "start <public-key>",
 	Short: "start the vpn for <public-key>",
-//	Args:  cobra.MinimumNArgs(1),
+	//	Args:  cobra.MinimumNArgs(1),
 	Run: func(cmd *cobra.Command, args []string) {
 		//check that a valid public key is provided
 		err := pubkey.Set(pk)
diff --git a/cmd/skywire-cli/commands/vpn/vvpnui.go b/cmd/skywire-cli/commands/vpn/vvpnui.go
index a3542c9e76..e133da3b9b 100644
--- a/cmd/skywire-cli/commands/vpn/vvpnui.go
+++ b/cmd/skywire-cli/commands/vpn/vvpnui.go
@@ -12,7 +12,6 @@ import (
 	clirpc "github.com/skycoin/skywire/cmd/skywire-cli/commands/rpc"
 	clivisor "github.com/skycoin/skywire/cmd/skywire-cli/commands/visor"
 	"github.com/skycoin/skywire/cmd/skywire-cli/internal"
-
 	"github.com/skycoin/skywire/pkg/visor/visorconfig"
 )
 

From 8684cb4fe8b4a8a388187f5bb4971e545a72105b Mon Sep 17 00:00:00 2001
From: Moses Narrow <36607567+0pcom@users.noreply.github.com>
Date: Thu, 23 Mar 2023 11:18:25 -0500
Subject: [PATCH 12/19] fix skywire-cli ut command

---
 cmd/skywire-cli/commands/ut/root.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/cmd/skywire-cli/commands/ut/root.go b/cmd/skywire-cli/commands/ut/root.go
index f94265ac87..3e883dc43a 100644
--- a/cmd/skywire-cli/commands/ut/root.go
+++ b/cmd/skywire-cli/commands/ut/root.go
@@ -41,6 +41,7 @@ func init() {
 var RootCmd = &cobra.Command{
 	Use:   "ut",
 	Short: "query uptime tracker",
+	Long:  "query uptime tracker\n Check local visor daily uptime percent with:\n skywire-cli ut -k $(skywire-cli visor pk)",
 	Run: func(cmd *cobra.Command, _ []string) {
 		if url == "" {
 			url = "http://ut.skywire.skycoin.com/uptimes?v=v2"
@@ -51,7 +52,7 @@ var RootCmd = &cobra.Command{
 			if err != nil {
 				internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Invalid or missing public key"))
 			} else {
-				url += url + "&visors=" + pubkey.String()
+				url += "&visors=" + pubkey.String()
 			}
 		}
 		utClient := http.Client{

From 8ae2bc9f40fd63d1a176aaa299a67bffb6f471a1 Mon Sep 17 00:00:00 2001
From: Moses Narrow <36607567+0pcom@users.noreply.github.com>
Date: Thu, 23 Mar 2023 11:45:24 -0500
Subject: [PATCH 13/19] fix various errors indicated by CI

---
 cmd/skywire-cli/commands/proxy/root.go |  2 --
 cmd/skywire-cli/commands/ut/root.go    | 23 +++++++++++------------
 cmd/skywire-cli/commands/vpn/vvpnui.go |  2 +-
 go.sum                                 |  1 -
 pkg/visor/rpc.go                       |  2 +-
 pkg/visor/visorconfig/values.go        |  1 +
 pkg/visor/visorconfig/values_linux.go  |  1 -
 7 files changed, 14 insertions(+), 18 deletions(-)

diff --git a/cmd/skywire-cli/commands/proxy/root.go b/cmd/skywire-cli/commands/proxy/root.go
index 23f5bd7c7d..ed11f18c48 100644
--- a/cmd/skywire-cli/commands/proxy/root.go
+++ b/cmd/skywire-cli/commands/proxy/root.go
@@ -8,8 +8,6 @@ import (
 )
 
 var (
-	path         string
-	isPkg        bool
 	isUnFiltered bool
 	ver          string
 	country      string
diff --git a/cmd/skywire-cli/commands/ut/root.go b/cmd/skywire-cli/commands/ut/root.go
index 3e883dc43a..c6586c3898 100644
--- a/cmd/skywire-cli/commands/ut/root.go
+++ b/cmd/skywire-cli/commands/ut/root.go
@@ -1,11 +1,10 @@
-// Package cliut root.go
-
+// Package cliut cmd/skywire-cli/ut/root.go
 package cliut
 
 import (
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"log"
 	"net/http"
 	"os"
@@ -70,23 +69,23 @@ var RootCmd = &cobra.Command{
 		}
 
 		if res.Body != nil {
-			defer res.Body.Close()
+			defer res.Body.Close() //nolint: errcheck
 		}
 
-		body, readErr := ioutil.ReadAll(res.Body)
+		body, readErr := io.ReadAll(res.Body)
 		if readErr != nil {
 			log.Fatal(readErr)
 		}
 
 		startDate := time.Date(now.Year(), now.Month(), -1, 0, 0, 0, 0, now.Location()).Format("2006-01-02")
 		endDate := time.Date(now.Year(), now.Month()+1, 1, 0, 0, 0, 0, now.Location()).Add(-1 * time.Second).Format("2006-01-02")
-		uptimes := Uptimes{}
-		jsonErr := json.Unmarshal(body, &uptimes)
+		uts := uptimes{}
+		jsonErr := json.Unmarshal(body, &uts)
 		if jsonErr != nil {
 			log.Fatal(jsonErr)
 		}
 		var msg []string
-		for _, j := range uptimes {
+		for _, j := range uts {
 			thisPk = j.Pk
 			if online {
 				if j.On {
@@ -102,7 +101,7 @@ var RootCmd = &cobra.Command{
 				os.Exit(0)
 			}
 			for _, i := range msg {
-				internal.PrintOutput(cmd.Flags(), fmt.Sprintf("%s", i), fmt.Sprintf("%s", i))
+				internal.PrintOutput(cmd.Flags(), i, i)
 			}
 		}
 	},
@@ -116,15 +115,15 @@ func selectedDaily(data map[string]string, startDate, endDate string) {
 				log.Fatal(err)
 			}
 			if utfloat >= float64(minUT) {
-				fmt.Printf(thisPk)
-				fmt.Printf(" ")
+				fmt.Print(thisPk)
+				fmt.Print(" ")
 				fmt.Println(date, uptime)
 			}
 		}
 	}
 }
 
-type Uptimes []struct {
+type uptimes []struct {
 	Pk    string            `json:"pk"`
 	Up    int               `json:"up"`
 	Down  int               `json:"down"`
diff --git a/cmd/skywire-cli/commands/vpn/vvpnui.go b/cmd/skywire-cli/commands/vpn/vvpnui.go
index e133da3b9b..bc26f794a5 100644
--- a/cmd/skywire-cli/commands/vpn/vvpnui.go
+++ b/cmd/skywire-cli/commands/vpn/vvpnui.go
@@ -22,7 +22,7 @@ func init() {
 	)
 	version := buildinfo.Version()
 	if version == "unknown" {
-		version = ""
+		version = "" //nolint
 	}
 	vpnUICmd.Flags().BoolVarP(&isPkg, "pkg", "p", false, "use package config path: "+visorconfig.SkywirePath)
 	vpnUICmd.Flags().StringVarP(&path, "config", "c", "", "config path")
diff --git a/go.sum b/go.sum
index a519883070..988e058c87 100644
--- a/go.sum
+++ b/go.sum
@@ -542,7 +542,6 @@ github.com/skycoin/noise v0.0.0-20180327030543-2492fe189ae6 h1:1Nc5EBY6pjfw1kwW0
 github.com/skycoin/noise v0.0.0-20180327030543-2492fe189ae6/go.mod h1:UXghlricA7J3aRD/k7p/zBObQfmBawwCxIVPVjz2Q3o=
 github.com/skycoin/skycoin v0.27.1 h1:HatxsRwVSPaV4qxH6290xPBmkH/HgiuAoY2qC+e8C9I=
 github.com/skycoin/skycoin v0.27.1/go.mod h1:78nHjQzd8KG0jJJVL/j0xMmrihXi70ti63fh8vXScJw=
-github.com/skycoin/skywire-utilities v0.0.0-20230110132024-c5536ba8e22c h1:jYHyLwSyRVR/TmT4WWIGAeFX4FawGHA4Gaeic0zX3KI=
 github.com/skycoin/skywire-utilities v0.0.0-20230110132024-c5536ba8e22c/go.mod h1:X5H+fKC3rD11/sm4t9V2FWy/aet7OdEilaO2Ar3waXY=
 github.com/skycoin/skywire-utilities v0.0.0-20230315234948-7c62dc34c53a h1:hTqQ+8G/2Y+vd4qXoTbm7gfj+mjil2zDnGSS8i8V4LQ=
 github.com/skycoin/skywire-utilities v0.0.0-20230315234948-7c62dc34c53a/go.mod h1:X5H+fKC3rD11/sm4t9V2FWy/aet7OdEilaO2Ar3waXY=
diff --git a/pkg/visor/rpc.go b/pkg/visor/rpc.go
index e6d40e7c78..2c008a16ae 100644
--- a/pkg/visor/rpc.go
+++ b/pkg/visor/rpc.go
@@ -651,7 +651,7 @@ func (r *RPC) VPNServers(vc *FilterServersIn, out *[]servicedisc.Service) (err e
 	return err
 }
 
-// VPNServers gets available public VPN server from service discovery URL
+// ProxyServers gets available socks5 proxy servers from service discovery URL
 func (r *RPC) ProxyServers(vc *FilterServersIn, out *[]servicedisc.Service) (err error) {
 	defer rpcutil.LogCall(r.log, "ProxyServers", nil)(out, &err)
 	proxyServers, err := r.visor.ProxyServers(vc.Version, vc.Country)
diff --git a/pkg/visor/visorconfig/values.go b/pkg/visor/visorconfig/values.go
index 0b2772c7de..3bdda25f7e 100644
--- a/pkg/visor/visorconfig/values.go
+++ b/pkg/visor/visorconfig/values.go
@@ -380,6 +380,7 @@ func SystemSurvey() (Survey, error) {
 	return s, nil
 }
 
+// PackageConfig returns the package-specific config paths
 func PackageConfig() skyenv.PkgConfig {
 	return skyenv.PackageConfig()
 }
diff --git a/pkg/visor/visorconfig/values_linux.go b/pkg/visor/visorconfig/values_linux.go
index cda6a6d589..4ba55897da 100644
--- a/pkg/visor/visorconfig/values_linux.go
+++ b/pkg/visor/visorconfig/values_linux.go
@@ -17,4 +17,3 @@ func UserConfig() skyenv.PkgConfig {
 	}
 	return usrConfig
 }
-

From 79cfb31f7cab05d8c989e58207d40a40281a3d1a Mon Sep 17 00:00:00 2001
From: Moses Narrow <36607567+0pcom@users.noreply.github.com>
Date: Thu, 23 Mar 2023 11:55:31 -0500
Subject: [PATCH 14/19] fix build constraints for skyenv package

---
 pkg/skyenv/skyenv_darwin.go  | 4 ++--
 pkg/skyenv/skyenv_windows.go | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/pkg/skyenv/skyenv_darwin.go b/pkg/skyenv/skyenv_darwin.go
index c5b050e1e5..7517a32bfe 100644
--- a/pkg/skyenv/skyenv_darwin.go
+++ b/pkg/skyenv/skyenv_darwin.go
@@ -1,5 +1,5 @@
-//go:build linux
-// +build linux
+//go:build darwin
+// +build darwin
 
 // Package skyenv defines variables and constants
 package skyenv
diff --git a/pkg/skyenv/skyenv_windows.go b/pkg/skyenv/skyenv_windows.go
index 4d1689f41e..8ac6adf91f 100644
--- a/pkg/skyenv/skyenv_windows.go
+++ b/pkg/skyenv/skyenv_windows.go
@@ -1,5 +1,5 @@
-//go:build linux
-// +build linux
+//go:build windows
+// +build windows
 
 // Package skyenv defines variables and constants
 package skyenv

From 67dd4926a62e3a23425e38653ff112c26117588c Mon Sep 17 00:00:00 2001
From: Moses Narrow <36607567+0pcom@users.noreply.github.com>
Date: Thu, 23 Mar 2023 12:07:34 -0500
Subject: [PATCH 15/19] temporary fix for survey error on windows & mac

---
 pkg/visor/visorconfig/values.go       | 55 ---------------------------
 pkg/visor/visorconfig/values_linux.go | 55 +++++++++++++++++++++++++++
 2 files changed, 55 insertions(+), 55 deletions(-)

diff --git a/pkg/visor/visorconfig/values.go b/pkg/visor/visorconfig/values.go
index 3bdda25f7e..65002850b7 100644
--- a/pkg/visor/visorconfig/values.go
+++ b/pkg/visor/visorconfig/values.go
@@ -325,61 +325,6 @@ var (
 	VisorConfigFile string
 )
 
-// Survey system hardware survey struct
-type Survey struct {
-	Timestamp      time.Time        `json:"timestamp"`
-	PubKey         cipher.PubKey    `json:"public_key,omitempty"`
-	SkycoinAddress string           `json:"skycoin_address,omitempty"`
-	GOOS           string           `json:"go_os,omitempty"`
-	GOARCH         string           `json:"go_arch,omitempty"`
-	SYSINFO        sysinfo.SysInfo  `json:"zcalusic_sysinfo,omitempty"`
-	IPInfo         *IPSkycoin       `json:"ip.skycoin.com,omitempty"`
-	IPAddr         *IPAddr          `json:"ip_addr,omitempty"`
-	Disks          *ghw.BlockInfo   `json:"ghw_blockinfo,omitempty"`
-	Product        *ghw.ProductInfo `json:"ghw_productinfo,omitempty"`
-	Memory         *ghw.MemoryInfo  `json:"ghw_memoryinfo,omitempty"`
-	UUID           uuid.UUID        `json:"uuid,omitempty"`
-	SkywireVersion string           `json:"skywire_version,omitempty"`
-}
-
-// SystemSurvey returns system survey
-func SystemSurvey() (Survey, error) {
-	var si sysinfo.SysInfo
-	si.GetSysInfo()
-	disks, err := ghw.Block()
-	if err != nil {
-		return Survey{}, err
-	}
-	product, err := ghw.Product()
-	if err != nil {
-		return Survey{}, err
-	}
-	memory, err := ghw.Memory()
-	if err != nil {
-		return Survey{}, err
-	}
-	for {
-		ipInfo := IPSkycoinFetch()
-		if ipInfo != nil {
-			break
-		}
-	}
-	s := Survey{
-		Timestamp:      time.Now(),
-		IPInfo:         IPSkycoinFetch(),
-		IPAddr:         IPA(),
-		GOOS:           runtime.GOOS,
-		GOARCH:         runtime.GOARCH,
-		SYSINFO:        si,
-		UUID:           uuid.New(),
-		Disks:          disks,
-		Product:        product,
-		Memory:         memory,
-		SkywireVersion: Version(),
-	}
-	return s, nil
-}
-
 // PackageConfig returns the package-specific config paths
 func PackageConfig() skyenv.PkgConfig {
 	return skyenv.PackageConfig()
diff --git a/pkg/visor/visorconfig/values_linux.go b/pkg/visor/visorconfig/values_linux.go
index 4ba55897da..c8f5e84242 100644
--- a/pkg/visor/visorconfig/values_linux.go
+++ b/pkg/visor/visorconfig/values_linux.go
@@ -17,3 +17,58 @@ func UserConfig() skyenv.PkgConfig {
 	}
 	return usrConfig
 }
+
+// Survey system hardware survey struct
+type Survey struct {
+	Timestamp      time.Time        `json:"timestamp"`
+	PubKey         cipher.PubKey    `json:"public_key,omitempty"`
+	SkycoinAddress string           `json:"skycoin_address,omitempty"`
+	GOOS           string           `json:"go_os,omitempty"`
+	GOARCH         string           `json:"go_arch,omitempty"`
+	SYSINFO        sysinfo.SysInfo  `json:"zcalusic_sysinfo,omitempty"`
+	IPInfo         *IPSkycoin       `json:"ip.skycoin.com,omitempty"`
+	IPAddr         *IPAddr          `json:"ip_addr,omitempty"`
+	Disks          *ghw.BlockInfo   `json:"ghw_blockinfo,omitempty"`
+	Product        *ghw.ProductInfo `json:"ghw_productinfo,omitempty"`
+	Memory         *ghw.MemoryInfo  `json:"ghw_memoryinfo,omitempty"`
+	UUID           uuid.UUID        `json:"uuid,omitempty"`
+	SkywireVersion string           `json:"skywire_version,omitempty"`
+}
+
+// SystemSurvey returns system survey
+func SystemSurvey() (Survey, error) {
+	var si sysinfo.SysInfo
+	si.GetSysInfo()
+	disks, err := ghw.Block()
+	if err != nil {
+		return Survey{}, err
+	}
+	product, err := ghw.Product()
+	if err != nil {
+		return Survey{}, err
+	}
+	memory, err := ghw.Memory()
+	if err != nil {
+		return Survey{}, err
+	}
+	for {
+		ipInfo := IPSkycoinFetch()
+		if ipInfo != nil {
+			break
+		}
+	}
+	s := Survey{
+		Timestamp:      time.Now(),
+		IPInfo:         IPSkycoinFetch(),
+		IPAddr:         IPA(),
+		GOOS:           runtime.GOOS,
+		GOARCH:         runtime.GOARCH,
+		SYSINFO:        si,
+		UUID:           uuid.New(),
+		Disks:          disks,
+		Product:        product,
+		Memory:         memory,
+		SkywireVersion: Version(),
+	}
+	return s, nil
+}

From 5bc63bc8a99309e2ac35ce5f7e2a2f4f5a62431b Mon Sep 17 00:00:00 2001
From: Moses Narrow <36607567+0pcom@users.noreply.github.com>
Date: Thu, 23 Mar 2023 12:16:36 -0500
Subject: [PATCH 16/19] fix imports

---
 pkg/visor/visorconfig/values.go       |  4 ----
 pkg/visor/visorconfig/values_linux.go | 12 +++++++++++-
 2 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/pkg/visor/visorconfig/values.go b/pkg/visor/visorconfig/values.go
index 65002850b7..21ebca578f 100644
--- a/pkg/visor/visorconfig/values.go
+++ b/pkg/visor/visorconfig/values.go
@@ -10,15 +10,11 @@ import (
 	"os/exec"
 	"os/user"
 	"path/filepath"
-	"runtime"
 	"strings"
 	"time"
 
 	"github.com/bitfield/script"
-	"github.com/google/uuid"
-	"github.com/jaypipes/ghw"
 	"github.com/skycoin/dmsg/pkg/dmsg"
-	"github.com/zcalusic/sysinfo"
 
 	"github.com/skycoin/skywire-utilities/pkg/buildinfo"
 	"github.com/skycoin/skywire-utilities/pkg/cipher"
diff --git a/pkg/visor/visorconfig/values_linux.go b/pkg/visor/visorconfig/values_linux.go
index c8f5e84242..39f61f1d0d 100644
--- a/pkg/visor/visorconfig/values_linux.go
+++ b/pkg/visor/visorconfig/values_linux.go
@@ -3,7 +3,17 @@
 
 package visorconfig
 
-import "github.com/skycoin/skywire/pkg/skyenv"
+import (
+	"runtime"
+	"time"
+
+	"github.com/google/uuid"
+	"github.com/jaypipes/ghw"
+	"github.com/zcalusic/sysinfo"
+
+	"github.com/skycoin/skywire-utilities/pkg/cipher"
+	"github.com/skycoin/skywire/pkg/skyenv"
+)
 
 // UserConfig contains installation paths for running skywire as the user
 func UserConfig() skyenv.PkgConfig {

From de604c2d2047b9ad40dcf49b4a1f798ea12a6da7 Mon Sep 17 00:00:00 2001
From: Moses Narrow <36607567+0pcom@users.noreply.github.com>
Date: Thu, 23 Mar 2023 12:25:02 -0500
Subject: [PATCH 17/19] fix survey logic for mac and windows

---
 pkg/visor/visorconfig/survey.go         |  3 ++
 pkg/visor/visorconfig/values_darwin.go  | 41 ++++++++++++++++++-
 pkg/visor/visorconfig/values_windows.go | 52 ++++++++++++++++++++++++-
 3 files changed, 94 insertions(+), 2 deletions(-)

diff --git a/pkg/visor/visorconfig/survey.go b/pkg/visor/visorconfig/survey.go
index c86c2aef73..0e51aa3e66 100644
--- a/pkg/visor/visorconfig/survey.go
+++ b/pkg/visor/visorconfig/survey.go
@@ -1,3 +1,6 @@
+//go:build linux
+// +build linux
+
 package visorconfig
 
 import (
diff --git a/pkg/visor/visorconfig/values_darwin.go b/pkg/visor/visorconfig/values_darwin.go
index 06c2d49281..cee5345b25 100644
--- a/pkg/visor/visorconfig/values_darwin.go
+++ b/pkg/visor/visorconfig/values_darwin.go
@@ -3,7 +3,15 @@
 
 package visorconfig
 
-import "github.com/skycoin/skywire/pkg/skyenv"
+import (
+	"runtime"
+
+	"github.com/google/uuid"
+	"github.com/jaypipes/ghw"
+
+	"github.com/skycoin/skywire-utilities/pkg/cipher"
+	"github.com/skycoin/skywire/pkg/skyenv"
+)
 
 // UserConfig contains installation paths for running skywire as the user
 func UserConfig() skyenv.PkgConfig {
@@ -17,3 +25,34 @@ func UserConfig() skyenv.PkgConfig {
 	}
 	return usrConfig
 }
+
+// Survey system hardware survey struct
+type Survey struct {
+	PubKey         cipher.PubKey  `json:"public_key,omitempty"`
+	SkycoinAddress string         `json:"skycoin_address,omitempty"`
+	GOOS           string         `json:"go_os,omitempty"`
+	GOARCH         string         `json:"go_arch,omitempty"`
+	IPInfo         *IPSkycoin     `json:"ip.skycoin.com,omitempty"`
+	IPAddr         *IPAddr        `json:"ip_addr,omitempty"`
+	Disks          *ghw.BlockInfo `json:"ghw_blockinfo,omitempty"`
+	UUID           uuid.UUID      `json:"uuid,omitempty"`
+	SkywireVersion string         `json:"skywire_version,omitempty"`
+}
+
+// SystemSurvey returns system survey
+func SystemSurvey() (Survey, error) {
+	disks, err := ghw.Block()
+	if err != nil {
+		return Survey{}, err
+	}
+	s := Survey{
+		IPInfo:         IPSkycoinFetch(),
+		IPAddr:         IPA(),
+		GOOS:           runtime.GOOS,
+		GOARCH:         runtime.GOARCH,
+		UUID:           uuid.New(),
+		Disks:          disks,
+		SkywireVersion: Version(),
+	}
+	return s, nil
+}
diff --git a/pkg/visor/visorconfig/values_windows.go b/pkg/visor/visorconfig/values_windows.go
index 10b904c2a6..7528799f8d 100644
--- a/pkg/visor/visorconfig/values_windows.go
+++ b/pkg/visor/visorconfig/values_windows.go
@@ -3,7 +3,15 @@
 
 package visorconfig
 
-import "github.com/skycoin/skywire/pkg/skyenv"
+import (
+	"runtime"
+
+	"github.com/google/uuid"
+	"github.com/jaypipes/ghw"
+
+	"github.com/skycoin/skywire-utilities/pkg/cipher"
+	"github.com/skycoin/skywire/pkg/skyenv"
+)
 
 // UserConfig contains installation paths for running skywire as the user
 func UserConfig() skyenv.PkgConfig {
@@ -17,3 +25,45 @@ func UserConfig() skyenv.PkgConfig {
 	}
 	return usrConfig
 }
+
+// Survey system hardware survey struct
+type Survey struct {
+	PubKey         cipher.PubKey    `json:"public_key,omitempty"`
+	SkycoinAddress string           `json:"skycoin_address,omitempty"`
+	GOOS           string           `json:"go_os,omitempty"`
+	GOARCH         string           `json:"go_arch,omitempty"`
+	IPInfo         *IPSkycoin       `json:"ip.skycoin.com,omitempty"`
+	IPAddr         *IPAddr          `json:"ip_addr,omitempty"`
+	Disks          *ghw.BlockInfo   `json:"ghw_blockinfo,omitempty"`
+	Product        *ghw.ProductInfo `json:"ghw_productinfo,omitempty"`
+	Memory         *ghw.MemoryInfo  `json:"ghw_memoryinfo,omitempty"`
+	UUID           uuid.UUID        `json:"uuid,omitempty"`
+	SkywireVersion string           `json:"skywire_version,omitempty"`
+}
+
+// SystemSurvey returns system survey
+func SystemSurvey() (Survey, error) {
+	disks, err := ghw.Block()
+	if err != nil {
+		return Survey{}, err
+	}
+	product, err := ghw.Product()
+	if err != nil {
+		return Survey{}, err
+	}
+	memory, err := ghw.Memory()
+	if err != nil {
+		return Survey{}, err
+	}
+	s := Survey{
+		IPInfo:         IPSkycoinFetch(),
+		GOOS:           runtime.GOOS,
+		GOARCH:         runtime.GOARCH,
+		UUID:           uuid.New(),
+		Disks:          disks,
+		Product:        product,
+		Memory:         memory,
+		SkywireVersion: Version(),
+	}
+	return s, nil
+}

From bd4adb79f73b340567ef915ddeef18599f7812c0 Mon Sep 17 00:00:00 2001
From: Moses Narrow <36607567+0pcom@users.noreply.github.com>
Date: Thu, 23 Mar 2023 12:32:17 -0500
Subject: [PATCH 18/19] remove build constraints for survey

---
 pkg/visor/visorconfig/survey.go | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/pkg/visor/visorconfig/survey.go b/pkg/visor/visorconfig/survey.go
index 0e51aa3e66..f1469af647 100644
--- a/pkg/visor/visorconfig/survey.go
+++ b/pkg/visor/visorconfig/survey.go
@@ -1,5 +1,4 @@
-//go:build linux
-// +build linux
+// Package visorconfig pkg/visor/visorconfig/survey.go
 
 package visorconfig
 

From 638078829dadb6fc83133bedc9ebbe63c8dad692 Mon Sep 17 00:00:00 2001
From: Moses Narrow <36607567+0pcom@users.noreply.github.com>
Date: Thu, 23 Mar 2023 12:34:34 -0500
Subject: [PATCH 19/19] fix package comments for survey

---
 pkg/visor/visorconfig/survey.go | 1 -
 1 file changed, 1 deletion(-)

diff --git a/pkg/visor/visorconfig/survey.go b/pkg/visor/visorconfig/survey.go
index f1469af647..c1a1b6cecb 100644
--- a/pkg/visor/visorconfig/survey.go
+++ b/pkg/visor/visorconfig/survey.go
@@ -1,5 +1,4 @@
 // Package visorconfig pkg/visor/visorconfig/survey.go
-
 package visorconfig
 
 import (