[client] Feat: Support Multiple Profiles (#3980)

[client] Feat: Support Multiple Profiles (#3980)
This commit is contained in:
hakansa
2025-07-25 16:54:46 +03:00
committed by GitHub
parent e0d9306b05
commit cb8b6ca59b
53 changed files with 4651 additions and 768 deletions

View File

@ -8,8 +8,10 @@ import (
"errors"
"flag"
"fmt"
"net/url"
"os"
"os/exec"
"os/user"
"path"
"runtime"
"strconv"
@ -34,11 +36,14 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"github.com/netbirdio/netbird/client/iface"
"github.com/netbirdio/netbird/client/internal"
"github.com/netbirdio/netbird/client/internal/profilemanager"
"github.com/netbirdio/netbird/client/proto"
"github.com/netbirdio/netbird/client/ui/desktop"
"github.com/netbirdio/netbird/client/ui/event"
"github.com/netbirdio/netbird/client/ui/process"
"github.com/netbirdio/netbird/util"
"github.com/netbirdio/netbird/version"
@ -54,11 +59,11 @@ const (
)
func main() {
daemonAddr, showSettings, showNetworks, showLoginURL, showDebug, errorMsg, saveLogsInFile := parseFlags()
flags := parseFlags()
// Initialize file logging if needed.
var logFile string
if saveLogsInFile {
if flags.saveLogsInFile {
file, err := initLogFile()
if err != nil {
log.Errorf("error while initializing log: %v", err)
@ -74,19 +79,28 @@ func main() {
a.SetIcon(fyne.NewStaticResource("netbird", iconDisconnected))
// Show error message window if needed.
if errorMsg != "" {
showErrorMessage(errorMsg)
if flags.errorMsg != "" {
showErrorMessage(flags.errorMsg)
return
}
// Create the service client (this also builds the settings or networks UI if requested).
client := newServiceClient(daemonAddr, logFile, a, showSettings, showNetworks, showLoginURL, showDebug)
client := newServiceClient(&newServiceClientArgs{
addr: flags.daemonAddr,
logFile: logFile,
app: a,
showSettings: flags.showSettings,
showNetworks: flags.showNetworks,
showLoginURL: flags.showLoginURL,
showDebug: flags.showDebug,
showProfiles: flags.showProfiles,
})
// Watch for theme/settings changes to update the icon.
go watchSettingsChanges(a, client)
// Run in window mode if any UI flag was set.
if showSettings || showNetworks || showDebug || showLoginURL {
if flags.showSettings || flags.showNetworks || flags.showDebug || flags.showLoginURL || flags.showProfiles {
a.Run()
return
}
@ -106,21 +120,35 @@ func main() {
systray.Run(client.onTrayReady, client.onTrayExit)
}
type cliFlags struct {
daemonAddr string
showSettings bool
showNetworks bool
showProfiles bool
showDebug bool
showLoginURL bool
errorMsg string
saveLogsInFile bool
}
// parseFlags reads and returns all needed command-line flags.
func parseFlags() (daemonAddr string, showSettings, showNetworks, showLoginURL, showDebug bool, errorMsg string, saveLogsInFile bool) {
func parseFlags() *cliFlags {
var flags cliFlags
defaultDaemonAddr := "unix:///var/run/netbird.sock"
if runtime.GOOS == "windows" {
defaultDaemonAddr = "tcp://127.0.0.1:41731"
}
flag.StringVar(&daemonAddr, "daemon-addr", defaultDaemonAddr, "Daemon service address to serve CLI requests [unix|tcp]://[path|host:port]")
flag.BoolVar(&showSettings, "settings", false, "run settings window")
flag.BoolVar(&showNetworks, "networks", false, "run networks window")
flag.BoolVar(&showLoginURL, "login-url", false, "show login URL in a popup window")
flag.BoolVar(&showDebug, "debug", false, "run debug window")
flag.StringVar(&errorMsg, "error-msg", "", "displays an error message window")
flag.BoolVar(&saveLogsInFile, "use-log-file", false, fmt.Sprintf("save logs in a file: %s/netbird-ui-PID.log", os.TempDir()))
flag.StringVar(&flags.daemonAddr, "daemon-addr", defaultDaemonAddr, "Daemon service address to serve CLI requests [unix|tcp]://[path|host:port]")
flag.BoolVar(&flags.showSettings, "settings", false, "run settings window")
flag.BoolVar(&flags.showNetworks, "networks", false, "run networks window")
flag.BoolVar(&flags.showProfiles, "profiles", false, "run profiles window")
flag.BoolVar(&flags.showDebug, "debug", false, "run debug window")
flag.StringVar(&flags.errorMsg, "error-msg", "", "displays an error message window")
flag.BoolVar(&flags.saveLogsInFile, "use-log-file", false, fmt.Sprintf("save logs in a file: %s/netbird-ui-PID.log", os.TempDir()))
flag.BoolVar(&flags.showLoginURL, "login-url", false, "show login URL in a popup window")
flag.Parse()
return
return &flags
}
// initLogFile initializes logging into a file.
@ -168,6 +196,12 @@ var iconConnectingMacOS []byte
//go:embed assets/netbird-systemtray-error-macos.png
var iconErrorMacOS []byte
//go:embed assets/connected.png
var iconConnectedDot []byte
//go:embed assets/disconnected.png
var iconDisconnectedDot []byte
type serviceClient struct {
ctx context.Context
cancel context.CancelFunc
@ -176,9 +210,13 @@ type serviceClient struct {
eventHandler *eventHandler
profileManager *profilemanager.ProfileManager
icAbout []byte
icConnected []byte
icConnectedDot []byte
icDisconnected []byte
icDisconnectedDot []byte
icUpdateConnected []byte
icUpdateDisconnected []byte
icConnecting []byte
@ -189,6 +227,7 @@ type serviceClient struct {
mUp *systray.MenuItem
mDown *systray.MenuItem
mSettings *systray.MenuItem
mProfile *profileMenu
mAbout *systray.MenuItem
mGitHub *systray.MenuItem
mVersionUI *systray.MenuItem
@ -214,7 +253,6 @@ type serviceClient struct {
// input elements for settings form
iMngURL *widget.Entry
iConfigFile *widget.Entry
iLogFile *widget.Entry
iPreSharedKey *widget.Entry
iInterfaceName *widget.Entry
@ -247,6 +285,7 @@ type serviceClient struct {
isUpdateIconActive bool
showNetworks bool
wNetworks fyne.Window
wProfiles fyne.Window
eventManager *event.Manager
@ -263,36 +302,50 @@ type menuHandler struct {
cancel context.CancelFunc
}
type newServiceClientArgs struct {
addr string
logFile string
app fyne.App
showSettings bool
showNetworks bool
showDebug bool
showLoginURL bool
showProfiles bool
}
// newServiceClient instance constructor
//
// This constructor also builds the UI elements for the settings window.
func newServiceClient(addr string, logFile string, a fyne.App, showSettings bool, showNetworks bool, showLoginURL bool, showDebug bool) *serviceClient {
func newServiceClient(args *newServiceClientArgs) *serviceClient {
ctx, cancel := context.WithCancel(context.Background())
s := &serviceClient{
ctx: ctx,
cancel: cancel,
addr: addr,
app: a,
logFile: logFile,
addr: args.addr,
app: args.app,
logFile: args.logFile,
sendNotification: false,
showAdvancedSettings: showSettings,
showNetworks: showNetworks,
showAdvancedSettings: args.showSettings,
showNetworks: args.showNetworks,
update: version.NewUpdate("nb/client-ui"),
}
s.eventHandler = newEventHandler(s)
s.profileManager = profilemanager.NewProfileManager()
s.setNewIcons()
switch {
case showSettings:
case args.showSettings:
s.showSettingsUI()
case showNetworks:
case args.showNetworks:
s.showNetworksUI()
case showLoginURL:
case args.showLoginURL:
s.showLoginURL()
case showDebug:
case args.showDebug:
s.showDebugUI()
case args.showProfiles:
s.showProfilesUI()
}
return s
@ -300,6 +353,8 @@ func newServiceClient(addr string, logFile string, a fyne.App, showSettings bool
func (s *serviceClient) setNewIcons() {
s.icAbout = iconAbout
s.icConnectedDot = iconConnectedDot
s.icDisconnectedDot = iconDisconnectedDot
if s.app.Settings().ThemeVariant() == theme.VariantDark {
s.icConnected = iconConnectedDark
s.icDisconnected = iconDisconnected
@ -342,8 +397,7 @@ func (s *serviceClient) showSettingsUI() {
s.wSettings.SetOnClosed(s.cancel)
s.iMngURL = widget.NewEntry()
s.iConfigFile = widget.NewEntry()
s.iConfigFile.Disable()
s.iLogFile = widget.NewEntry()
s.iLogFile.Disable()
s.iPreSharedKey = widget.NewPasswordEntry()
@ -368,14 +422,22 @@ func (s *serviceClient) showSettingsUI() {
// getSettingsForm to embed it into settings window.
func (s *serviceClient) getSettingsForm() *widget.Form {
var activeProfName string
activeProf, err := s.profileManager.GetActiveProfile()
if err != nil {
log.Errorf("get active profile: %v", err)
} else {
activeProfName = activeProf.Name
}
return &widget.Form{
Items: []*widget.FormItem{
{Text: "Profile", Widget: widget.NewLabel(activeProfName)},
{Text: "Quantum-Resistance", Widget: s.sRosenpassPermissive},
{Text: "Interface Name", Widget: s.iInterfaceName},
{Text: "Interface Port", Widget: s.iInterfacePort},
{Text: "Management URL", Widget: s.iMngURL},
{Text: "Pre-shared Key", Widget: s.iPreSharedKey},
{Text: "Config File", Widget: s.iConfigFile},
{Text: "Log File", Widget: s.iLogFile},
{Text: "Network Monitor", Widget: s.sNetworkMonitor},
{Text: "Disable DNS", Widget: s.sDisableDNS},
@ -416,27 +478,67 @@ func (s *serviceClient) getSettingsForm() *widget.Form {
s.managementURL = iMngURL
s.preSharedKey = s.iPreSharedKey.Text
loginRequest := proto.LoginRequest{
ManagementUrl: iMngURL,
IsUnixDesktopClient: runtime.GOOS == "linux" || runtime.GOOS == "freebsd",
RosenpassPermissive: &s.sRosenpassPermissive.Checked,
InterfaceName: &s.iInterfaceName.Text,
WireguardPort: &port,
NetworkMonitor: &s.sNetworkMonitor.Checked,
DisableDns: &s.sDisableDNS.Checked,
DisableClientRoutes: &s.sDisableClientRoutes.Checked,
DisableServerRoutes: &s.sDisableServerRoutes.Checked,
BlockLanAccess: &s.sBlockLANAccess.Checked,
}
if s.iPreSharedKey.Text != censoredPreSharedKey {
loginRequest.OptionalPreSharedKey = &s.iPreSharedKey.Text
}
if err := s.restartClient(&loginRequest); err != nil {
log.Errorf("restarting client connection: %v", err)
currUser, err := user.Current()
if err != nil {
log.Errorf("get current user: %v", err)
return
}
var req proto.SetConfigRequest
req.ProfileName = activeProf.Name
req.Username = currUser.Username
if iMngURL != "" {
req.ManagementUrl = iMngURL
}
req.RosenpassPermissive = &s.sRosenpassPermissive.Checked
req.InterfaceName = &s.iInterfaceName.Text
req.WireguardPort = &port
req.NetworkMonitor = &s.sNetworkMonitor.Checked
req.DisableDns = &s.sDisableDNS.Checked
req.DisableClientRoutes = &s.sDisableClientRoutes.Checked
req.DisableServerRoutes = &s.sDisableServerRoutes.Checked
req.BlockLanAccess = &s.sBlockLANAccess.Checked
if s.iPreSharedKey.Text != censoredPreSharedKey {
req.OptionalPreSharedKey = &s.iPreSharedKey.Text
}
conn, err := s.getSrvClient(failFastTimeout)
if err != nil {
log.Errorf("get client: %v", err)
dialog.ShowError(fmt.Errorf("Failed to connect to the service: %v", err), s.wSettings)
return
}
_, err = conn.SetConfig(s.ctx, &req)
if err != nil {
log.Errorf("set config: %v", err)
dialog.ShowError(fmt.Errorf("Failed to set configuration: %v", err), s.wSettings)
return
}
status, err := conn.Status(s.ctx, &proto.StatusRequest{})
if err != nil {
log.Errorf("get service status: %v", err)
dialog.ShowError(fmt.Errorf("Failed to get service status: %v", err), s.wSettings)
return
}
if status.Status == string(internal.StatusConnected) {
// run down & up
_, err = conn.Down(s.ctx, &proto.DownRequest{})
if err != nil {
log.Errorf("down service: %v", err)
}
_, err = conn.Up(s.ctx, &proto.UpRequest{})
if err != nil {
log.Errorf("up service: %v", err)
dialog.ShowError(fmt.Errorf("Failed to reconnect: %v", err), s.wSettings)
return
}
}
}
},
OnCancel: func() {
@ -452,8 +554,21 @@ func (s *serviceClient) login(openURL bool) (*proto.LoginResponse, error) {
return nil, err
}
activeProf, err := s.profileManager.GetActiveProfile()
if err != nil {
log.Errorf("get active profile: %v", err)
return nil, err
}
currUser, err := user.Current()
if err != nil {
return nil, fmt.Errorf("get current user: %w", err)
}
loginResp, err := conn.Login(s.ctx, &proto.LoginRequest{
IsUnixDesktopClient: runtime.GOOS == "linux" || runtime.GOOS == "freebsd",
ProfileName: &activeProf.Name,
Username: &currUser.Username,
})
if err != nil {
log.Errorf("login to management URL with: %v", err)
@ -461,15 +576,9 @@ func (s *serviceClient) login(openURL bool) (*proto.LoginResponse, error) {
}
if loginResp.NeedsSSOLogin && openURL {
err = open.Run(loginResp.VerificationURIComplete)
err = s.handleSSOLogin(loginResp, conn)
if err != nil {
log.Errorf("opening the verification uri in the browser failed: %v", err)
return nil, err
}
_, err = conn.WaitSSOLogin(s.ctx, &proto.WaitSSOLoginRequest{UserCode: loginResp.UserCode})
if err != nil {
log.Errorf("waiting sso login failed with: %v", err)
log.Errorf("handle SSO login failed: %v", err)
return nil, err
}
}
@ -477,6 +586,34 @@ func (s *serviceClient) login(openURL bool) (*proto.LoginResponse, error) {
return loginResp, nil
}
func (s *serviceClient) handleSSOLogin(loginResp *proto.LoginResponse, conn proto.DaemonServiceClient) error {
err := open.Run(loginResp.VerificationURIComplete)
if err != nil {
log.Errorf("opening the verification uri in the browser failed: %v", err)
return err
}
resp, err := conn.WaitSSOLogin(s.ctx, &proto.WaitSSOLoginRequest{UserCode: loginResp.UserCode})
if err != nil {
log.Errorf("waiting sso login failed with: %v", err)
return err
}
if resp.Email != "" {
err := s.profileManager.SetActiveProfileState(&profilemanager.ProfileState{
Email: resp.Email,
})
if err != nil {
log.Warnf("failed to set profile state: %v", err)
} else {
s.mProfile.refresh()
}
}
return nil
}
func (s *serviceClient) menuUpClick() error {
systray.SetTemplateIcon(iconConnectingMacOS, s.icConnecting)
conn, err := s.getSrvClient(defaultFailTimeout)
@ -575,6 +712,7 @@ func (s *serviceClient) updateStatus() error {
}
systray.SetTooltip("NetBird (Connected)")
s.mStatus.SetTitle("Connected")
s.mStatus.SetIcon(s.icConnectedDot)
s.mUp.Disable()
s.mDown.Enable()
s.mNetworks.Enable()
@ -634,6 +772,7 @@ func (s *serviceClient) setDisconnectedStatus() {
}
systray.SetTooltip("NetBird (Disconnected)")
s.mStatus.SetTitle("Disconnected")
s.mStatus.SetIcon(s.icDisconnectedDot)
s.mDown.Disable()
s.mUp.Enable()
s.mNetworks.Disable()
@ -658,7 +797,13 @@ func (s *serviceClient) onTrayReady() {
// setup systray menu items
s.mStatus = systray.AddMenuItem("Disconnected", "Disconnected")
s.mStatus.SetIcon(s.icDisconnectedDot)
s.mStatus.Disable()
profileMenuItem := systray.AddMenuItem("", "")
emailMenuItem := systray.AddMenuItem("", "")
s.mProfile = newProfileMenu(s.ctx, s.profileManager, *s.eventHandler, profileMenuItem, emailMenuItem, s.menuDownClick, s.menuUpClick, s.getSrvClient, s.loadSettings)
systray.AddSeparator()
s.mUp = systray.AddMenuItem("Connect", "Connect")
s.mDown = systray.AddMenuItem("Disconnect", "Disconnect")
@ -790,7 +935,15 @@ func (s *serviceClient) getSrvClient(timeout time.Duration) (proto.DaemonService
// getSrvConfig from the service to show it in the settings window.
func (s *serviceClient) getSrvConfig() {
s.managementURL = internal.DefaultManagementURL
s.managementURL = profilemanager.DefaultManagementURL
_, err := s.profileManager.GetActiveProfile()
if err != nil {
log.Errorf("get active profile: %v", err)
return
}
var cfg *profilemanager.Config
conn, err := s.getSrvClient(failFastTimeout)
if err != nil {
@ -798,48 +951,63 @@ func (s *serviceClient) getSrvConfig() {
return
}
cfg, err := conn.GetConfig(s.ctx, &proto.GetConfigRequest{})
currUser, err := user.Current()
if err != nil {
log.Errorf("get current user: %v", err)
return
}
activeProf, err := s.profileManager.GetActiveProfile()
if err != nil {
log.Errorf("get active profile: %v", err)
return
}
srvCfg, err := conn.GetConfig(s.ctx, &proto.GetConfigRequest{
ProfileName: activeProf.Name,
Username: currUser.Username,
})
if err != nil {
log.Errorf("get config settings from server: %v", err)
return
}
if cfg.ManagementUrl != "" {
s.managementURL = cfg.ManagementUrl
cfg = protoConfigToConfig(srvCfg)
if cfg.ManagementURL.String() != "" {
s.managementURL = cfg.ManagementURL.String()
}
s.preSharedKey = cfg.PreSharedKey
s.RosenpassPermissive = cfg.RosenpassPermissive
s.interfaceName = cfg.InterfaceName
s.interfacePort = int(cfg.WireguardPort)
s.interfaceName = cfg.WgIface
s.interfacePort = cfg.WgPort
s.networkMonitor = cfg.NetworkMonitor
s.disableDNS = cfg.DisableDns
s.networkMonitor = *cfg.NetworkMonitor
s.disableDNS = cfg.DisableDNS
s.disableClientRoutes = cfg.DisableClientRoutes
s.disableServerRoutes = cfg.DisableServerRoutes
s.blockLANAccess = cfg.BlockLanAccess
s.blockLANAccess = cfg.BlockLANAccess
if s.showAdvancedSettings {
s.iMngURL.SetText(s.managementURL)
s.iConfigFile.SetText(cfg.ConfigFile)
s.iLogFile.SetText(cfg.LogFile)
s.iPreSharedKey.SetText(cfg.PreSharedKey)
s.iInterfaceName.SetText(cfg.InterfaceName)
s.iInterfacePort.SetText(strconv.Itoa(int(cfg.WireguardPort)))
s.iInterfaceName.SetText(cfg.WgIface)
s.iInterfacePort.SetText(strconv.Itoa(cfg.WgPort))
s.sRosenpassPermissive.SetChecked(cfg.RosenpassPermissive)
if !cfg.RosenpassEnabled {
s.sRosenpassPermissive.Disable()
}
s.sNetworkMonitor.SetChecked(cfg.NetworkMonitor)
s.sDisableDNS.SetChecked(cfg.DisableDns)
s.sNetworkMonitor.SetChecked(*cfg.NetworkMonitor)
s.sDisableDNS.SetChecked(cfg.DisableDNS)
s.sDisableClientRoutes.SetChecked(cfg.DisableClientRoutes)
s.sDisableServerRoutes.SetChecked(cfg.DisableServerRoutes)
s.sBlockLANAccess.SetChecked(cfg.BlockLanAccess)
s.sBlockLANAccess.SetChecked(cfg.BlockLANAccess)
}
if s.mNotifications == nil {
return
}
if cfg.DisableNotifications {
if cfg.DisableNotifications != nil && *cfg.DisableNotifications {
s.mNotifications.Uncheck()
} else {
s.mNotifications.Check()
@ -849,6 +1017,58 @@ func (s *serviceClient) getSrvConfig() {
}
}
func protoConfigToConfig(cfg *proto.GetConfigResponse) *profilemanager.Config {
var config profilemanager.Config
if cfg.ManagementUrl != "" {
parsed, err := url.Parse(cfg.ManagementUrl)
if err != nil {
log.Errorf("parse management URL: %v", err)
} else {
config.ManagementURL = parsed
}
}
if cfg.PreSharedKey != "" {
if cfg.PreSharedKey != censoredPreSharedKey {
config.PreSharedKey = cfg.PreSharedKey
} else {
config.PreSharedKey = ""
}
}
if cfg.AdminURL != "" {
parsed, err := url.Parse(cfg.AdminURL)
if err != nil {
log.Errorf("parse admin URL: %v", err)
} else {
config.AdminURL = parsed
}
}
config.WgIface = cfg.InterfaceName
if cfg.WireguardPort != 0 {
config.WgPort = int(cfg.WireguardPort)
} else {
config.WgPort = iface.DefaultWgPort
}
config.DisableAutoConnect = cfg.DisableAutoConnect
config.ServerSSHAllowed = &cfg.ServerSSHAllowed
config.RosenpassEnabled = cfg.RosenpassEnabled
config.RosenpassPermissive = cfg.RosenpassPermissive
config.DisableNotifications = &cfg.DisableNotifications
config.LazyConnectionEnabled = cfg.LazyConnectionEnabled
config.BlockInbound = cfg.BlockInbound
config.NetworkMonitor = &cfg.NetworkMonitor
config.DisableDNS = cfg.DisableDns
config.DisableClientRoutes = cfg.DisableClientRoutes
config.DisableServerRoutes = cfg.DisableServerRoutes
config.BlockLANAccess = cfg.BlockLanAccess
return &config
}
func (s *serviceClient) onUpdateAvailable() {
s.updateIndicationLock.Lock()
defer s.updateIndicationLock.Unlock()
@ -880,7 +1100,22 @@ func (s *serviceClient) loadSettings() {
return
}
cfg, err := conn.GetConfig(s.ctx, &proto.GetConfigRequest{})
currUser, err := user.Current()
if err != nil {
log.Errorf("get current user: %v", err)
return
}
activeProf, err := s.profileManager.GetActiveProfile()
if err != nil {
log.Errorf("get active profile: %v", err)
return
}
cfg, err := conn.GetConfig(s.ctx, &proto.GetConfigRequest{
ProfileName: activeProf.Name,
Username: currUser.Username,
})
if err != nil {
log.Errorf("get config settings from server: %v", err)
return
@ -936,41 +1171,37 @@ func (s *serviceClient) updateConfig() error {
blockInbound := s.mBlockInbound.Checked()
notificationsDisabled := !s.mNotifications.Checked()
loginRequest := proto.LoginRequest{
IsUnixDesktopClient: runtime.GOOS == "linux" || runtime.GOOS == "freebsd",
activeProf, err := s.profileManager.GetActiveProfile()
if err != nil {
log.Errorf("get active profile: %v", err)
return err
}
currUser, err := user.Current()
if err != nil {
log.Errorf("get current user: %v", err)
return err
}
conn, err := s.getSrvClient(failFastTimeout)
if err != nil {
log.Errorf("get client: %v", err)
return err
}
req := proto.SetConfigRequest{
ProfileName: activeProf.Name,
Username: currUser.Username,
DisableAutoConnect: &disableAutoStart,
ServerSSHAllowed: &sshAllowed,
RosenpassEnabled: &rosenpassEnabled,
DisableAutoConnect: &disableAutoStart,
DisableNotifications: &notificationsDisabled,
LazyConnectionEnabled: &lazyConnectionEnabled,
BlockInbound: &blockInbound,
DisableNotifications: &notificationsDisabled,
}
if err := s.restartClient(&loginRequest); err != nil {
log.Errorf("restarting client connection: %v", err)
return err
}
return nil
}
// restartClient restarts the client connection.
func (s *serviceClient) restartClient(loginRequest *proto.LoginRequest) error {
ctx, cancel := context.WithTimeout(s.ctx, defaultFailTimeout)
defer cancel()
client, err := s.getSrvClient(failFastTimeout)
if err != nil {
return err
}
_, err = client.Login(ctx, loginRequest)
if err != nil {
return err
}
_, err = client.Up(ctx, &proto.UpRequest{})
if err != nil {
if _, err := conn.SetConfig(s.ctx, &req); err != nil {
log.Errorf("set config settings on server: %v", err)
return err
}