Add Settings window to Agent UI

Agent systray UI has been extended with
a setting window that allows configuring 
management URL, admin URL and 
supports pre-shared key.
While for the Netbird managed version 
the Settings are not necessary, it helps
to properly configure the self-hosted version.
This commit is contained in:
Givi Khojanashvili
2022-04-15 19:30:12 +04:00
committed by GitHub
parent 196207402d
commit 951e011a9c
25 changed files with 767 additions and 244 deletions

View File

@@ -3,6 +3,7 @@ package cmd
import (
"context"
"fmt"
"github.com/netbirdio/netbird/util"
log "github.com/sirupsen/logrus"
@@ -28,7 +29,7 @@ var loginCmd = &cobra.Command{
// workaround to run without service
if logFile == "console" {
config, err := internal.GetConfig(managementURL, configPath, preSharedKey)
config, err := internal.GetConfig(managementURL, adminURL, configPath, preSharedKey)
if err != nil {
log.Errorf("get config file: %v", err)
return err
@@ -56,7 +57,7 @@ var loginCmd = &cobra.Command{
request := proto.LoginRequest{
SetupKey: setupKey,
PresharedKey: preSharedKey,
PreSharedKey: preSharedKey,
ManagementUrl: managementURL,
}
client := proto.NewDaemonServiceClient(conn)

View File

@@ -28,6 +28,7 @@ var (
logFile string
daemonAddr string
managementURL string
adminURL string
setupKey string
preSharedKey string
rootCmd = &cobra.Command{
@@ -56,6 +57,7 @@ func init() {
}
rootCmd.PersistentFlags().StringVar(&daemonAddr, "daemon-addr", defaultDaemonAddr, "Daemon service address to serve CLI requests [unix|tcp]://[path|host:port]")
rootCmd.PersistentFlags().StringVar(&managementURL, "management-url", "", fmt.Sprintf("Management Service URL [http|https]://[host]:[port] (default \"%s\")", internal.ManagementURLDefault().String()))
rootCmd.PersistentFlags().StringVar(&adminURL, "admin-url", "https://app.netbird.io", "Admin Panel URL [http|https]://[host]:[port]")
rootCmd.PersistentFlags().StringVar(&configPath, "config", defaultConfigPath, "Wiretrustee config file location")
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "sets Wiretrustee log level")
rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Wiretrustee log path. If console is specified the the log will be output to stdout")

View File

@@ -54,7 +54,7 @@ func (p *program) Start(svc service.Service) error {
}
}
serverInstance := server.New(p.ctx, managementURL, configPath)
serverInstance := server.New(p.ctx, managementURL, adminURL, configPath, logFile)
if err := serverInstance.Start(); err != nil {
log.Fatalf("failed start daemon: %v", err)
}

View File

@@ -93,7 +93,7 @@ func startClientDaemon(
}
s := grpc.NewServer()
server := client.New(ctx, managementURL, configPath)
server := client.New(ctx, managementURL, adminURL, configPath, "")
if err := server.Start(); err != nil {
t.Fatal(err)
}

View File

@@ -27,7 +27,7 @@ var upCmd = &cobra.Command{
// workaround to run without service
if logFile == "console" {
config, err := internal.GetConfig(managementURL, configPath, preSharedKey)
config, err := internal.GetConfig(managementURL, adminURL, configPath, preSharedKey)
if err != nil {
log.Errorf("get config file: %v", err)
return err
@@ -57,7 +57,7 @@ var upCmd = &cobra.Command{
loginRequest := proto.LoginRequest{
SetupKey: setupKey,
PresharedKey: preSharedKey,
PreSharedKey: preSharedKey,
ManagementUrl: managementURL,
}
err = WithBackOff(func() error {

View File

@@ -2,12 +2,13 @@ package internal
import (
"fmt"
"net/url"
"os"
"github.com/netbirdio/netbird/iface"
"github.com/netbirdio/netbird/util"
log "github.com/sirupsen/logrus"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"net/url"
"os"
)
var managementURLDefault *url.URL
@@ -17,7 +18,7 @@ func ManagementURLDefault() *url.URL {
}
func init() {
managementURL, err := parseManagementURL("https://api.wiretrustee.com:33073")
managementURL, err := parseURL("Management URL", "https://api.wiretrustee.com:33073")
if err != nil {
panic(err)
}
@@ -30,16 +31,17 @@ type Config struct {
PrivateKey string
PreSharedKey string
ManagementURL *url.URL
AdminURL *url.URL
WgIface string
IFaceBlackList []string
}
//createNewConfig creates a new config generating a new Wireguard key and saving to file
func createNewConfig(managementURL string, configPath string, preSharedKey string) (*Config, error) {
// createNewConfig creates a new config generating a new Wireguard key and saving to file
func createNewConfig(managementURL, adminURL, configPath, preSharedKey string) (*Config, error) {
wgKey := generateKey()
config := &Config{PrivateKey: wgKey, WgIface: iface.WgInterfaceDefault, IFaceBlackList: []string{}}
if managementURL != "" {
URL, err := parseManagementURL(managementURL)
URL, err := parseURL("Management URL", managementURL)
if err != nil {
return nil, err
}
@@ -62,55 +64,82 @@ func createNewConfig(managementURL string, configPath string, preSharedKey strin
return config, nil
}
func parseManagementURL(managementURL string) (*url.URL, error) {
func parseURL(serviceName, managementURL string) (*url.URL, error) {
parsedMgmtURL, err := url.ParseRequestURI(managementURL)
if err != nil {
log.Errorf("failed parsing management URL %s: [%s]", managementURL, err.Error())
return nil, err
}
if !(parsedMgmtURL.Scheme == "https" || parsedMgmtURL.Scheme == "http") {
return nil, fmt.Errorf("invalid Management Service URL provided %s. Supported format [http|https]://[host]:[port]", managementURL)
if parsedMgmtURL.Scheme != "https" && parsedMgmtURL.Scheme != "http" {
return nil, fmt.Errorf(
"invalid %s URL provided %s. Supported format [http|https]://[host]:[port]",
serviceName, managementURL)
}
return parsedMgmtURL, err
}
// ReadConfig reads existing config. In case provided managementURL is not empty overrides the read property
func ReadConfig(managementURL string, configPath string) (*Config, error) {
func ReadConfig(managementURL, adminURL, configPath string, preSharedKey *string) (*Config, error) {
config := &Config{}
_, err := util.ReadJson(configPath, config)
if err != nil {
if _, err := util.ReadJson(configPath, config); err != nil {
return nil, err
}
refresh := false
if managementURL != "" && config.ManagementURL.String() != managementURL {
URL, err := parseManagementURL(managementURL)
log.Infof("new Management URL provided, updated to %s (old value %s)",
managementURL, config.ManagementURL)
newURL, err := parseURL("Management URL", managementURL)
if err != nil {
return nil, err
}
config.ManagementURL = URL
// since we have new management URL, we need to update config file
err = util.WriteJson(configPath, config)
if err != nil {
return nil, err
}
log.Infof("new Management URL provided, updated to %s (old value %s)", managementURL, config.ManagementURL)
config.ManagementURL = newURL
refresh = true
}
return config, err
if adminURL != "" && (config.AdminURL == nil || config.AdminURL.String() != adminURL) {
log.Infof("new Admin Panel URL provided, updated to %s (old value %s)",
adminURL, config.AdminURL)
newURL, err := parseURL("Admin Panel URL", adminURL)
if err != nil {
return nil, err
}
config.AdminURL = newURL
refresh = true
}
if preSharedKey != nil && config.PreSharedKey != *preSharedKey {
log.Infof("new pre-shared key provided, updated to %s (old value %s)",
*preSharedKey, config.PreSharedKey)
config.PreSharedKey = *preSharedKey
refresh = true
}
if refresh {
// since we have new management URL, we need to update config file
if err := util.WriteJson(configPath, config); err != nil {
return nil, err
}
}
return config, nil
}
// GetConfig reads existing config or generates a new one
func GetConfig(managementURL string, configPath string, preSharedKey string) (*Config, error) {
func GetConfig(managementURL, adminURL, configPath, preSharedKey string) (*Config, error) {
if _, err := os.Stat(configPath); os.IsNotExist(err) {
log.Infof("generating new config %s", configPath)
return createNewConfig(managementURL, configPath, preSharedKey)
return createNewConfig(managementURL, adminURL, configPath, preSharedKey)
} else {
return ReadConfig(managementURL, configPath)
// don't overwrite pre-shared key if we receive asterisks from UI
pk := &preSharedKey
if preSharedKey == "**********" {
pk = nil
}
return ReadConfig(managementURL, adminURL, configPath, pk)
}
}

View File

@@ -2,24 +2,25 @@ package internal
import (
"errors"
"github.com/netbirdio/netbird/util"
"github.com/stretchr/testify/assert"
"os"
"path/filepath"
"testing"
"github.com/netbirdio/netbird/util"
"github.com/stretchr/testify/assert"
)
func TestReadConfig(t *testing.T) {
}
func TestGetConfig(t *testing.T) {
func TestGetConfig(t *testing.T) {
managementURL := "https://test.management.url:33071"
adminURL := "https://app.admin.url"
path := filepath.Join(t.TempDir(), "config.json")
preSharedKey := "preSharedKey"
// case 1: new config has to be generated
config, err := GetConfig(managementURL, path, preSharedKey)
config, err := GetConfig(managementURL, adminURL, path, preSharedKey)
if err != nil {
return
}
@@ -32,7 +33,7 @@ func TestGetConfig(t *testing.T) {
}
// case 2: existing config -> fetch it
config, err = GetConfig(managementURL, path, preSharedKey)
config, err = GetConfig(managementURL, adminURL, path, preSharedKey)
if err != nil {
return
}
@@ -42,7 +43,7 @@ func TestGetConfig(t *testing.T) {
// case 3: existing config, but new managementURL has been provided -> update config
newManagementURL := "https://test.newManagement.url:33071"
config, err = GetConfig(newManagementURL, path, preSharedKey)
config, err = GetConfig(newManagementURL, adminURL, path, preSharedKey)
if err != nil {
return
}
@@ -56,5 +57,4 @@ func TestGetConfig(t *testing.T) {
return
}
assert.Equal(t, readConf.(*Config).ManagementURL.String(), newManagementURL)
}

View File

@@ -2,9 +2,7 @@ package internal
import (
"context"
"time"
"github.com/cenkalti/backoff/v4"
"github.com/google/uuid"
"github.com/netbirdio/netbird/client/system"
mgm "github.com/netbirdio/netbird/management/client"
@@ -16,16 +14,6 @@ import (
)
func Login(ctx context.Context, config *Config, setupKey string) error {
backOff := &backoff.ExponentialBackOff{
InitialInterval: time.Second,
RandomizationFactor: backoff.DefaultRandomizationFactor,
Multiplier: backoff.DefaultMultiplier,
MaxInterval: 2 * time.Second,
MaxElapsedTime: time.Second * 10,
Stop: backoff.Stop,
Clock: backoff.SystemClock,
}
// validate our peer's Wireguard PRIVATE key
myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey)
if err != nil {
@@ -38,41 +26,29 @@ func Login(ctx context.Context, config *Config, setupKey string) error {
mgmTlsEnabled = true
}
loginOp := func() error {
log.Debugf("connecting to Management Service %s", config.ManagementURL.String())
mgmClient, err := mgm.NewClient(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
if err != nil {
log.Errorf("failed connecting to Management Service %s %v", config.ManagementURL.String(), err)
return err
}
log.Debugf("connected to management Service %s", config.ManagementURL.String())
log.Debugf("connecting to Management Service %s", config.ManagementURL.String())
mgmClient, err := mgm.NewClient(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
if err != nil {
log.Errorf("failed connecting to Management Service %s %v", config.ManagementURL.String(), err)
return err
}
log.Debugf("connected to management Service %s", config.ManagementURL.String())
serverKey, err := mgmClient.GetServerPublicKey()
if err != nil {
log.Errorf("failed while getting Management Service public key: %v", err)
return err
}
_, err = loginPeer(*serverKey, mgmClient, setupKey)
if err != nil {
log.Errorf("failed logging-in peer on Management Service : %v", err)
return err
}
err = mgmClient.Close()
if err != nil {
log.Errorf("failed closing Management Service client: %v", err)
return err
}
return nil
serverKey, err := mgmClient.GetServerPublicKey()
if err != nil {
log.Errorf("failed while getting Management Service public key: %v", err)
return err
}
err = backoff.RetryNotify(loginOp, backOff, func(err error, duration time.Duration) {
log.Warnf("retrying Login to the Management service in %v due to error %v", duration, err)
})
_, err = loginPeer(*serverKey, mgmClient, setupKey)
if err != nil {
log.Errorf("exiting login retry loop due to unrecoverable error: %v", err)
log.Errorf("failed logging-in peer on Management Service : %v", err)
return err
}
err = mgmClient.Close()
if err != nil {
log.Errorf("failed closing Management Service client: %v", err)
return err
}

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.17.3
// protoc-gen-go v1.25.0-devel
// protoc v3.14.0
// source: daemon.proto
package proto
@@ -28,10 +28,12 @@ type LoginRequest struct {
// setupKey wiretrustee setup key.
SetupKey string `protobuf:"bytes,1,opt,name=setupKey,proto3" json:"setupKey,omitempty"`
// presharedKey for wireguard setup.
PresharedKey string `protobuf:"bytes,2,opt,name=presharedKey,proto3" json:"presharedKey,omitempty"`
// preSharedKey for wireguard setup.
PreSharedKey string `protobuf:"bytes,2,opt,name=preSharedKey,proto3" json:"preSharedKey,omitempty"`
// managementUrl to authenticate.
ManagementUrl string `protobuf:"bytes,3,opt,name=managementUrl,proto3" json:"managementUrl,omitempty"`
// adminUrl to manage keys.
AdminURL string `protobuf:"bytes,4,opt,name=adminURL,proto3" json:"adminURL,omitempty"`
}
func (x *LoginRequest) Reset() {
@@ -73,9 +75,9 @@ func (x *LoginRequest) GetSetupKey() string {
return ""
}
func (x *LoginRequest) GetPresharedKey() string {
func (x *LoginRequest) GetPreSharedKey() string {
if x != nil {
return x.PresharedKey
return x.PreSharedKey
}
return ""
}
@@ -87,6 +89,13 @@ func (x *LoginRequest) GetManagementUrl() string {
return ""
}
func (x *LoginRequest) GetAdminURL() string {
if x != nil {
return x.AdminURL
}
return ""
}
type LoginResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -363,44 +372,185 @@ func (*DownResponse) Descriptor() ([]byte, []int) {
return file_daemon_proto_rawDescGZIP(), []int{7}
}
type GetConfigRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *GetConfigRequest) Reset() {
*x = GetConfigRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_daemon_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetConfigRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetConfigRequest) ProtoMessage() {}
func (x *GetConfigRequest) ProtoReflect() protoreflect.Message {
mi := &file_daemon_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetConfigRequest.ProtoReflect.Descriptor instead.
func (*GetConfigRequest) Descriptor() ([]byte, []int) {
return file_daemon_proto_rawDescGZIP(), []int{8}
}
type GetConfigResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// managementUrl settings value.
ManagementUrl string `protobuf:"bytes,1,opt,name=managementUrl,proto3" json:"managementUrl,omitempty"`
// configFile settings value.
ConfigFile string `protobuf:"bytes,2,opt,name=configFile,proto3" json:"configFile,omitempty"`
// logFile settings value.
LogFile string `protobuf:"bytes,3,opt,name=logFile,proto3" json:"logFile,omitempty"`
// preSharedKey settings value.
PreSharedKey string `protobuf:"bytes,4,opt,name=preSharedKey,proto3" json:"preSharedKey,omitempty"`
// adminURL settings value.
AdminURL string `protobuf:"bytes,5,opt,name=adminURL,proto3" json:"adminURL,omitempty"`
}
func (x *GetConfigResponse) Reset() {
*x = GetConfigResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_daemon_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetConfigResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetConfigResponse) ProtoMessage() {}
func (x *GetConfigResponse) ProtoReflect() protoreflect.Message {
mi := &file_daemon_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetConfigResponse.ProtoReflect.Descriptor instead.
func (*GetConfigResponse) Descriptor() ([]byte, []int) {
return file_daemon_proto_rawDescGZIP(), []int{9}
}
func (x *GetConfigResponse) GetManagementUrl() string {
if x != nil {
return x.ManagementUrl
}
return ""
}
func (x *GetConfigResponse) GetConfigFile() string {
if x != nil {
return x.ConfigFile
}
return ""
}
func (x *GetConfigResponse) GetLogFile() string {
if x != nil {
return x.LogFile
}
return ""
}
func (x *GetConfigResponse) GetPreSharedKey() string {
if x != nil {
return x.PreSharedKey
}
return ""
}
func (x *GetConfigResponse) GetAdminURL() string {
if x != nil {
return x.AdminURL
}
return ""
}
var File_daemon_proto protoreflect.FileDescriptor
var file_daemon_proto_rawDesc = []byte{
0x0a, 0x0c, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06,
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x74, 0x0a, 0x0c, 0x4c, 0x6f, 0x67, 0x69,
0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x75,
0x70, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x74, 0x75,
0x70, 0x4b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x73, 0x68, 0x61, 0x72, 0x65,
0x64, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x73,
0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x6e, 0x61,
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x22, 0x0f,
0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x0b, 0x0a, 0x09, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0c, 0x0a, 0x0a,
0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0f, 0x0a, 0x0d, 0x53, 0x74,
0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x28, 0x0a, 0x0e, 0x53,
0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a,
0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73,
0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x0d, 0x0a, 0x0b, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x32, 0xe6, 0x01, 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12,
0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c,
0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d,
0x0a, 0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e,
0x2e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a,
0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e,
0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16,
0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x90, 0x01, 0x0a, 0x0c, 0x4c, 0x6f, 0x67,
0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x74,
0x75, 0x70, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x74,
0x75, 0x70, 0x4b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72,
0x65, 0x64, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65,
0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x6e,
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x12,
0x1a, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x18, 0x04, 0x20, 0x01, 0x28,
0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x22, 0x0f, 0x0a, 0x0d, 0x4c,
0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0b, 0x0a, 0x09,
0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0c, 0x0a, 0x0a, 0x55, 0x70, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0f, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75,
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x28, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74,
0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74,
0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74,
0x75, 0x73, 0x22, 0x0d, 0x0a, 0x0b, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb3, 0x01, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6d,
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72,
0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x69, 0x6c,
0x65, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01,
0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x70,
0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12,
0x1a, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x18, 0x05, 0x20, 0x01, 0x28,
0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x32, 0xaa, 0x02, 0x0a, 0x0d,
0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a,
0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x64,
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64, 0x61,
0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12,
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15,
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x04, 0x44, 0x6f, 0x77, 0x6e,
0x12, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44,
0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a,
0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53,
0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
0x33, 0x0a, 0x04, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e,
0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x64,
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x12, 0x18, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x64, 0x61,
0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -415,28 +565,32 @@ func file_daemon_proto_rawDescGZIP() []byte {
return file_daemon_proto_rawDescData
}
var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_daemon_proto_goTypes = []interface{}{
(*LoginRequest)(nil), // 0: daemon.LoginRequest
(*LoginResponse)(nil), // 1: daemon.LoginResponse
(*UpRequest)(nil), // 2: daemon.UpRequest
(*UpResponse)(nil), // 3: daemon.UpResponse
(*StatusRequest)(nil), // 4: daemon.StatusRequest
(*StatusResponse)(nil), // 5: daemon.StatusResponse
(*DownRequest)(nil), // 6: daemon.DownRequest
(*DownResponse)(nil), // 7: daemon.DownResponse
(*LoginRequest)(nil), // 0: daemon.LoginRequest
(*LoginResponse)(nil), // 1: daemon.LoginResponse
(*UpRequest)(nil), // 2: daemon.UpRequest
(*UpResponse)(nil), // 3: daemon.UpResponse
(*StatusRequest)(nil), // 4: daemon.StatusRequest
(*StatusResponse)(nil), // 5: daemon.StatusResponse
(*DownRequest)(nil), // 6: daemon.DownRequest
(*DownResponse)(nil), // 7: daemon.DownResponse
(*GetConfigRequest)(nil), // 8: daemon.GetConfigRequest
(*GetConfigResponse)(nil), // 9: daemon.GetConfigResponse
}
var file_daemon_proto_depIdxs = []int32{
0, // 0: daemon.DaemonService.Login:input_type -> daemon.LoginRequest
2, // 1: daemon.DaemonService.Up:input_type -> daemon.UpRequest
4, // 2: daemon.DaemonService.Status:input_type -> daemon.StatusRequest
6, // 3: daemon.DaemonService.Down:input_type -> daemon.DownRequest
1, // 4: daemon.DaemonService.Login:output_type -> daemon.LoginResponse
3, // 5: daemon.DaemonService.Up:output_type -> daemon.UpResponse
5, // 6: daemon.DaemonService.Status:output_type -> daemon.StatusResponse
7, // 7: daemon.DaemonService.Down:output_type -> daemon.DownResponse
4, // [4:8] is the sub-list for method output_type
0, // [0:4] is the sub-list for method input_type
8, // 4: daemon.DaemonService.GetConfig:input_type -> daemon.GetConfigRequest
1, // 5: daemon.DaemonService.Login:output_type -> daemon.LoginResponse
3, // 6: daemon.DaemonService.Up:output_type -> daemon.UpResponse
5, // 7: daemon.DaemonService.Status:output_type -> daemon.StatusResponse
7, // 8: daemon.DaemonService.Down:output_type -> daemon.DownResponse
9, // 9: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse
5, // [5:10] is the sub-list for method output_type
0, // [0:5] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
@@ -544,6 +698,30 @@ func file_daemon_proto_init() {
return nil
}
}
file_daemon_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetConfigRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_daemon_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetConfigResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
@@ -551,7 +729,7 @@ func file_daemon_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_daemon_proto_rawDesc,
NumEnums: 0,
NumMessages: 8,
NumMessages: 10,
NumExtensions: 0,
NumServices: 1,
},

View File

@@ -18,17 +18,23 @@ service DaemonService {
// Down engine work in the daemon.
rpc Down(DownRequest) returns (DownResponse) {}
// GetConfig of the daemon.
rpc GetConfig(GetConfigRequest) returns (GetConfigResponse) {}
};
message LoginRequest {
// setupKey wiretrustee setup key.
string setupKey = 1;
// presharedKey for wireguard setup.
string presharedKey = 2;
// preSharedKey for wireguard setup.
string preSharedKey = 2;
// managementUrl to authenticate.
string managementUrl = 3;
// adminUrl to manage keys.
string adminURL = 4;
}
message LoginResponse {}
@@ -47,3 +53,22 @@ message StatusResponse{
message DownRequest {}
message DownResponse {}
message GetConfigRequest {}
message GetConfigResponse {
// managementUrl settings value.
string managementUrl = 1;
// configFile settings value.
string configFile = 2;
// logFile settings value.
string logFile = 3;
// preSharedKey settings value.
string preSharedKey = 4;
// adminURL settings value.
string adminURL = 5;
}

View File

@@ -26,6 +26,8 @@ type DaemonServiceClient interface {
Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error)
// Down engine work in the daemon.
Down(ctx context.Context, in *DownRequest, opts ...grpc.CallOption) (*DownResponse, error)
// GetConfig of the daemon.
GetConfig(ctx context.Context, in *GetConfigRequest, opts ...grpc.CallOption) (*GetConfigResponse, error)
}
type daemonServiceClient struct {
@@ -72,6 +74,15 @@ func (c *daemonServiceClient) Down(ctx context.Context, in *DownRequest, opts ..
return out, nil
}
func (c *daemonServiceClient) GetConfig(ctx context.Context, in *GetConfigRequest, opts ...grpc.CallOption) (*GetConfigResponse, error) {
out := new(GetConfigResponse)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/GetConfig", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// DaemonServiceServer is the server API for DaemonService service.
// All implementations must embed UnimplementedDaemonServiceServer
// for forward compatibility
@@ -84,6 +95,8 @@ type DaemonServiceServer interface {
Status(context.Context, *StatusRequest) (*StatusResponse, error)
// Down engine work in the daemon.
Down(context.Context, *DownRequest) (*DownResponse, error)
// GetConfig of the daemon.
GetConfig(context.Context, *GetConfigRequest) (*GetConfigResponse, error)
mustEmbedUnimplementedDaemonServiceServer()
}
@@ -103,6 +116,9 @@ func (UnimplementedDaemonServiceServer) Status(context.Context, *StatusRequest)
func (UnimplementedDaemonServiceServer) Down(context.Context, *DownRequest) (*DownResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Down not implemented")
}
func (UnimplementedDaemonServiceServer) GetConfig(context.Context, *GetConfigRequest) (*GetConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetConfig not implemented")
}
func (UnimplementedDaemonServiceServer) mustEmbedUnimplementedDaemonServiceServer() {}
// UnsafeDaemonServiceServer may be embedded to opt out of forward compatibility for this service.
@@ -188,6 +204,24 @@ func _DaemonService_Down_Handler(srv interface{}, ctx context.Context, dec func(
return interceptor(ctx, in, info, handler)
}
func _DaemonService_GetConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetConfigRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(DaemonServiceServer).GetConfig(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/daemon.DaemonService/GetConfig",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaemonServiceServer).GetConfig(ctx, req.(*GetConfigRequest))
}
return interceptor(ctx, in, info, handler)
}
// DaemonService_ServiceDesc is the grpc.ServiceDesc for DaemonService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@@ -211,6 +245,10 @@ var DaemonService_ServiceDesc = grpc.ServiceDesc{
MethodName: "Down",
Handler: _DaemonService_Down_Handler,
},
{
MethodName: "GetConfig",
Handler: _DaemonService_GetConfig_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "daemon.proto",

View File

@@ -17,7 +17,9 @@ type Server struct {
actCancel context.CancelFunc
managementURL string
adminURL string
configPath string
logFile string
mutex sync.Mutex
config *internal.Config
@@ -25,11 +27,13 @@ type Server struct {
}
// New server instance constructor.
func New(ctx context.Context, managementURL, configPath string) *Server {
func New(ctx context.Context, managementURL, adminURL, configPath, logFile string) *Server {
return &Server{
rootCtx: ctx,
managementURL: managementURL,
adminURL: adminURL,
configPath: configPath,
logFile: logFile,
}
}
@@ -52,7 +56,7 @@ func (s *Server) Start() error {
s.actCancel = cancel
// if configuration exists, we just start connections.
config, err := internal.ReadConfig(s.managementURL, s.configPath)
config, err := internal.ReadConfig(s.managementURL, s.adminURL, s.configPath, nil)
if err != nil {
log.Warnf("no config file, skip connection stage: %v", err)
return nil
@@ -71,22 +75,42 @@ func (s *Server) Start() error {
// Login uses setup key to prepare configuration for the daemon.
func (s *Server) Login(_ context.Context, msg *proto.LoginRequest) (*proto.LoginResponse, error) {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.actCancel != nil {
s.actCancel()
}
ctx, cancel := context.WithCancel(s.rootCtx)
s.actCancel = cancel
s.mutex.Unlock()
state := internal.CtxGetState(ctx)
defer state.Set(internal.StatusIdle)
state.Set(internal.StatusConnecting)
s.mutex.Lock()
managementURL := s.managementURL
if msg.ManagementUrl != "" {
managementURL = msg.ManagementUrl
}
config, err := internal.GetConfig(managementURL, s.configPath, msg.PresharedKey)
adminURL := s.adminURL
if msg.AdminURL != "" {
adminURL = msg.AdminURL
}
s.mutex.Unlock()
config, err := internal.GetConfig(managementURL, adminURL, s.configPath, msg.PreSharedKey)
if err != nil {
return nil, err
}
s.mutex.Lock()
s.config = config
s.mutex.Unlock()
// login operation uses backoff scheme to connect to management API
// we don't wait for result and return response immediately.
if err := internal.Login(s.rootCtx, s.config, msg.SetupKey); err != nil {
if err := internal.Login(ctx, s.config, msg.SetupKey); err != nil {
log.Errorf("failed login: %v", err)
return nil, err
}
@@ -158,3 +182,32 @@ func (s *Server) Status(ctx context.Context, msg *proto.StatusRequest) (*proto.S
return &proto.StatusResponse{Status: string(status)}, nil
}
// GetConfig of the daemon.
func (s *Server) GetConfig(ctx context.Context, msg *proto.GetConfigRequest) (*proto.GetConfigResponse, error) {
s.mutex.Lock()
defer s.mutex.Unlock()
managementURL := s.managementURL
if managementURL == "" && s.config.ManagementURL != nil {
managementURL = s.config.ManagementURL.String()
}
adminURL := s.adminURL
if s.config.AdminURL != nil {
adminURL = s.config.AdminURL.String()
}
preSharedKey := s.config.PreSharedKey
if preSharedKey != "" {
preSharedKey = "**********"
}
return &proto.GetConfigResponse{
ManagementUrl: managementURL,
AdminURL: adminURL,
ConfigFile: s.configPath,
LogFile: s.logFile,
PreSharedKey: preSharedKey,
}, nil
}

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"runtime"
"strconv"
@@ -20,18 +21,24 @@ import (
"github.com/netbirdio/netbird/client/proto"
log "github.com/sirupsen/logrus"
"github.com/skratchdot/open-golang/open"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/widget"
)
const (
defaulFailTimeout = time.Duration(3 * time.Second)
fastFailTimeout = time.Second
)
func main() {
var daemonAddr string
if err := checkPIDFile(); err != nil {
fmt.Println(err)
return
}
defaultDaemonAddr := "unix:///var/run/wiretrustee.sock"
if runtime.GOOS == "windows" {
defaultDaemonAddr = "tcp://127.0.0.1:41731"
@@ -42,37 +49,177 @@ func main() {
defaultDaemonAddr,
"Daemon service address to serve CLI requests [unix|tcp]://[path|host:port]")
var showSettings bool
flag.BoolVar(&showSettings, "settings", false, "run settings windows")
flag.Parse()
client := newServiceClient(daemonAddr)
systray.Run(client.onTrayReady, client.onTrayExit)
a := app.New()
client := newServiceClient(daemonAddr, a, showSettings)
if showSettings {
a.Run()
} else {
if err := checkPIDFile(); err != nil {
fmt.Println(err)
return
}
systray.Run(client.onTrayReady, client.onTrayExit)
}
}
//go:embed connected.ico
var iconConnected []byte
var iconConnectedICO []byte
//go:embed connected.png
var iconConnectedPNG []byte
//go:embed disconnected.ico
var iconDisconnected []byte
var iconDisconnectedICO []byte
//go:embed disconnected.png
var iconDisconnectedPNG []byte
type serviceClient struct {
ctx context.Context
addr string
conn proto.DaemonServiceClient
mStatus *systray.MenuItem
mUp *systray.MenuItem
mDown *systray.MenuItem
ctx context.Context
addr string
conn proto.DaemonServiceClient
icConnected []byte
icDisconnected []byte
// systray menu itmes
mStatus *systray.MenuItem
mUp *systray.MenuItem
mDown *systray.MenuItem
mAdminPanel *systray.MenuItem
mSettings *systray.MenuItem
mQuit *systray.MenuItem
// application with main windows.
app fyne.App
wSettings fyne.Window
showSettings bool
// input elements for settings form
iMngURL *widget.Entry
iAdminURL *widget.Entry
iConfigFile *widget.Entry
iLogFile *widget.Entry
iPreSharedKey *widget.Entry
// observable settings over correspondign iMngURL and iPreSharedKey values.
managementURL string
preSharedKey string
adminURL string
}
func newServiceClient(addr string) *serviceClient {
// newServiceClient instance constructor
//
// This constructor olso build UI elements for settings window.
func newServiceClient(addr string, a fyne.App, showSettings bool) *serviceClient {
s := &serviceClient{
ctx: context.Background(),
addr: addr,
app: a,
showSettings: showSettings,
}
if runtime.GOOS == "windows" {
s.icConnected = iconConnectedICO
s.icDisconnected = iconDisconnectedICO
} else {
s.icConnected = iconConnectedPNG
s.icDisconnected = iconDisconnectedPNG
}
if showSettings {
s.showUIElements()
return s
}
return s
}
func (s *serviceClient) up() error {
conn, err := s.client()
func (s *serviceClient) showUIElements() {
// add settings window UI elements.
s.wSettings = s.app.NewWindow("Settings")
s.iMngURL = widget.NewEntry()
s.iAdminURL = widget.NewEntry()
s.iConfigFile = widget.NewEntry()
s.iConfigFile.Disable()
s.iLogFile = widget.NewEntry()
s.iLogFile.Disable()
s.iPreSharedKey = widget.NewPasswordEntry()
s.wSettings.SetContent(s.getSettingsForm())
s.wSettings.Resize(fyne.NewSize(600, 100))
s.getSrvConfig()
s.wSettings.Show()
}
// getSettingsForm to embed it into settings window.
func (s *serviceClient) getSettingsForm() *widget.Form {
return &widget.Form{
Items: []*widget.FormItem{
{Text: "Management URL", Widget: s.iMngURL},
{Text: "Admin URL", Widget: s.iAdminURL},
{Text: "Pre-shared Key", Widget: s.iPreSharedKey},
{Text: "Config File", Widget: s.iConfigFile},
{Text: "Log File", Widget: s.iLogFile},
},
SubmitText: "Save",
OnSubmit: func() {
if s.iPreSharedKey.Text != "" && s.iPreSharedKey.Text != "**********" {
// validate preSharedKey if it added
if _, err := wgtypes.ParseKey(s.iPreSharedKey.Text); err != nil {
dialog.ShowError(fmt.Errorf("Invalid Pre-shared Key Value"), s.wSettings)
return
}
}
defer s.wSettings.Close()
// if management URL or Pre-shared key changed, we try to re-login with new settings.
if s.managementURL != s.iMngURL.Text || s.preSharedKey != s.iPreSharedKey.Text ||
s.adminURL != s.iAdminURL.Text {
s.managementURL = s.iMngURL.Text
s.preSharedKey = s.iPreSharedKey.Text
s.adminURL = s.iAdminURL.Text
client, err := s.getSrvClient(fastFailTimeout)
if err != nil {
log.Errorf("get daemon client: %v", err)
return
}
_, err = client.Login(s.ctx, &proto.LoginRequest{
ManagementUrl: s.iMngURL.Text,
AdminURL: s.iAdminURL.Text,
PreSharedKey: s.iPreSharedKey.Text,
})
if err != nil {
log.Errorf("login to management URL: %v", err)
return
}
_, err = client.Up(s.ctx, &proto.UpRequest{})
if err != nil {
log.Errorf("login to management URL: %v", err)
return
}
}
s.wSettings.Close()
},
OnCancel: func() {
s.wSettings.Close()
},
}
}
func (s *serviceClient) menuUpClick() error {
conn, err := s.getSrvClient(defaulFailTimeout)
if err != nil {
log.Errorf("get client: %v", err)
return err
@@ -97,8 +244,8 @@ func (s *serviceClient) up() error {
return nil
}
func (s *serviceClient) down() error {
conn, err := s.client()
func (s *serviceClient) menuDownClick() error {
conn, err := s.getSrvClient(defaulFailTimeout)
if err != nil {
log.Errorf("get client: %v", err)
return err
@@ -124,7 +271,7 @@ func (s *serviceClient) down() error {
}
func (s *serviceClient) updateStatus() {
conn, err := s.client()
conn, err := s.getSrvClient(defaulFailTimeout)
if err != nil {
log.Errorf("get client: %v", err)
return
@@ -137,12 +284,12 @@ func (s *serviceClient) updateStatus() {
}
if status.Status == string(internal.StatusConnected) {
systray.SetTemplateIcon(iconConnected, iconConnected)
systray.SetIcon(s.icConnected)
s.mStatus.SetTitle("Connected")
s.mUp.Disable()
s.mDown.Enable()
} else {
systray.SetTemplateIcon(iconDisconnected, iconDisconnected)
systray.SetIcon(s.icDisconnected)
s.mStatus.SetTitle("Disconnected")
s.mDown.Disable()
s.mUp.Enable()
@@ -150,25 +297,23 @@ func (s *serviceClient) updateStatus() {
}
func (s *serviceClient) onTrayReady() {
systray.SetTemplateIcon(iconDisconnected, iconDisconnected)
systray.SetIcon(s.icDisconnected)
// setup systray menu items
s.mStatus = systray.AddMenuItem("Disconnected", "Disconnected")
s.mStatus.Disable()
systray.AddSeparator()
s.mUp = systray.AddMenuItem("Connect", "Connect")
s.mDown = systray.AddMenuItem("Disconnect", "Disconnect")
s.mDown.Disable()
mURL := systray.AddMenuItem("Admin Panel", "Wiretrustee Admin Panel")
s.mAdminPanel = systray.AddMenuItem("Admin Panel", "Wiretrustee Admin Panel")
systray.AddSeparator()
mQuit := systray.AddMenuItem("Quit", "Quit the client app")
s.mSettings = systray.AddMenuItem("Settings", "Settings of the application")
systray.AddSeparator()
s.mQuit = systray.AddMenuItem("Quit", "Quit the client app")
go func() {
s.getSrvConfig()
for {
s.updateStatus()
time.Sleep(time.Second * 3)
@@ -179,19 +324,42 @@ func (s *serviceClient) onTrayReady() {
var err error
for {
select {
case <-mURL.ClickedCh:
err = open.Run("https://app.wiretrustee.com")
case <-s.mAdminPanel.ClickedCh:
err = open.Run(s.adminURL)
case <-s.mUp.ClickedCh:
s.mUp.Disable()
if err = s.up(); err != nil {
if err = s.menuUpClick(); err != nil {
s.mUp.Enable()
}
case <-s.mDown.ClickedCh:
s.mDown.Disable()
if err = s.down(); err != nil {
if err = s.menuDownClick(); err != nil {
s.mDown.Enable()
}
case <-mQuit.ClickedCh:
case <-s.mSettings.ClickedCh:
s.mSettings.Disable()
go func() {
defer s.mSettings.Enable()
proc, err := os.Executable()
if err != nil {
log.Errorf("show settings: %v", err)
return
}
cmd := exec.Command(proc, "--settings=true")
out, err := cmd.CombinedOutput()
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
log.Errorf("start settings UI: %v, %s", err, string(out))
return
}
if len(out) != 0 {
log.Info("settings change:", string(out))
}
// update config in systray when settings windows closed
s.getSrvConfig()
}()
case <-s.mQuit.ClickedCh:
systray.Quit()
return
}
@@ -204,12 +372,13 @@ func (s *serviceClient) onTrayReady() {
func (s *serviceClient) onTrayExit() {}
func (s *serviceClient) client() (proto.DaemonServiceClient, error) {
// getSrvClient connection to the service.
func (s *serviceClient) getSrvClient(timeout time.Duration) (proto.DaemonServiceClient, error) {
if s.conn != nil {
return s.conn, nil
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
conn, err := grpc.DialContext(
@@ -226,6 +395,40 @@ func (s *serviceClient) client() (proto.DaemonServiceClient, error) {
return s.conn, nil
}
// getSrvConfig from the service to show it in the settings window.
func (s *serviceClient) getSrvConfig() {
s.managementURL = "https://api.wiretrustee.com:33073"
s.adminURL = "https://app.netbird.io"
conn, err := s.getSrvClient(fastFailTimeout)
if err != nil {
log.Errorf("get client: %v", err)
return
}
cfg, err := conn.GetConfig(s.ctx, &proto.GetConfigRequest{})
if err != nil {
log.Errorf("get config settings from server: %v", err)
return
}
if cfg.ManagementUrl != "" {
s.managementURL = cfg.ManagementUrl
}
if cfg.AdminURL != "" {
s.adminURL = cfg.AdminURL
}
s.preSharedKey = cfg.PreSharedKey
if s.showSettings {
s.iMngURL.SetText(s.managementURL)
s.iAdminURL.SetText(s.adminURL)
s.iConfigFile.SetText(cfg.ConfigFile)
s.iLogFile.SetText(cfg.LogFile)
s.iPreSharedKey.SetText(cfg.PreSharedKey)
}
}
// checkPIDFile exists and return error, or write new.
func checkPIDFile() error {
pidFile := path.Join(os.TempDir(), "wiretrustee-ui.pid")

BIN
client/ui/connected.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
client/ui/disconnected.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -0,0 +1,8 @@
[Desktop Entry]
Name=Netbird Agent
Exec=/usr/bin/wiretrustee-ui
Icon=netbird
Type=Application
Terminal=false
Categories=Utility;
Keywords=netbird;

View File

@@ -1,9 +0,0 @@
#!/usr/bin/env xdg-open
[Desktop Entry]
Version=1.0
Terminal=false
Type=Application
Name=Wiretrustee UI
Exec=/usr/bin/wiretrustee-ui
Icon=/usr/share/icons/hicolor/256x256/wiretrustee.png
Comment=Wiretrustee client UI.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB