From 1dd622020bc4fe9aa8613da05dff3d6229d2a2d2 Mon Sep 17 00:00:00 2001 From: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> Date: Tue, 6 Aug 2024 22:55:45 +0200 Subject: [PATCH 1/5] feat: profile switching (multiple networks) --- client/cmd/profile.go | 216 ++++++++++++++++++++++++++++++++++++++++++ client/cmd/root.go | 4 + 2 files changed, 220 insertions(+) create mode 100644 client/cmd/profile.go diff --git a/client/cmd/profile.go b/client/cmd/profile.go new file mode 100644 index 00000000000..bfd55a11066 --- /dev/null +++ b/client/cmd/profile.go @@ -0,0 +1,216 @@ +package cmd + +import ( + "context" + "fmt" + "os" + "path" + "strings" + "time" + + "github.com/kardianos/service" + "github.com/netbirdio/netbird/client/internal" + "github.com/netbirdio/netbird/client/proto" + "github.com/spf13/cobra" +) + +func stopServiceIfRunning(cmd *cobra.Command) (func() error, error) { + s, err := newSVC(newProgram(context.WithCancel(cmd.Context())), newSVCConfig()) + if err != nil { + return nil, err + } + + status, err := s.Status() + if err != nil { + return nil, err + } + + if status == service.StatusRunning { + if err := s.Stop(); err != nil { + return nil, err + } + + return s.Start, nil + } + + return func() error { return nil }, nil +} + +func disconnectClientIfConnected(cmd *cobra.Command) error { + ctx := internal.CtxInitState(cmd.Context()) + + resp, err := getStatus(ctx) + if err != nil { + return err + } + + if resp.GetStatus() == string(internal.StatusConnected) || resp.GetStatus() == string(internal.StatusConnecting) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*7) + defer cancel() + + conn, err := DialClientGRPCServer(ctx, daemonAddr) + if err != nil { + return fmt.Errorf("failed to connect to service CLI interface %v", err) + } + + defer conn.Close() + + daemonClient := proto.NewDaemonServiceClient(conn) + + if _, err := daemonClient.Down(ctx, &proto.DownRequest{}); err != nil { + return fmt.Errorf("call service down method: %v", err) + } + } + + return nil +} + +func exists(path string) bool { + _, err := os.Stat(path) + return !os.IsNotExist(err) +} + +func ensureDefaultProfile(profilesPath string, cmd *cobra.Command) (bool, error) { + defaultPath := path.Join(profilesPath, "default.json") + + if !exists(defaultPath) { + if err := os.Rename(configPath, defaultPath); err != nil { + return false, fmt.Errorf("failed to move config.json to default.json: %v", err) + } + + // Create a symlink to the default profile + if err := os.Symlink(defaultPath, configPath); err != nil { + return false, fmt.Errorf("failed to create symlink to default profile: %v", err) + } + + return true, nil + } + + return false, nil +} + +func getProfilesPath() (string, error) { + profilesPath := path.Join(defaultConfigPathDir, "profiles") + + if err := os.MkdirAll(profilesPath, os.ModePerm); err != nil { + return "", fmt.Errorf("failed to create profiles directory: %v", err) + } + + return profilesPath, nil +} + +var ( + profileCmd = &cobra.Command{ + Use: "profile", + Short: "manages different profiles", + } + + profileListCmd = &cobra.Command{ + Use: "list", + Short: "list all profiles", + RunE: func(cmd *cobra.Command, args []string) error { + ensureProfileErr := make(chan error) + + profilesPath, err := getProfilesPath() + if err != nil { + return err + } + + // Defer this so the profiles are displayed without the service stopping + // blocking the thread + go func() { + shouldRestart, err := ensureDefaultProfile(profilesPath, cmd) + + if err != nil { + ensureProfileErr <- err + return + } + + if shouldRestart { + start, err := stopServiceIfRunning(cmd) + if err != nil { + ensureProfileErr <- err + return + } + + if err := disconnectClientIfConnected(cmd); err != nil { + ensureProfileErr <- err + return + } + + if err := start(); err != nil { + ensureProfileErr <- err + return + } + } + + ensureProfileErr <- nil + }() + + defer close(ensureProfileErr) + + entries, err := os.ReadDir(profilesPath) + if err != nil { + return fmt.Errorf("failed to read profiles directory: %v", err) + } + + for _, entry := range entries { + if entry.IsDir() { + continue + } + + fmt.Println(strings.TrimSuffix(entry.Name(), ".json")) + } + + if err := <-ensureProfileErr; err != nil { + return err + } + + return nil + }, + } + + profileSwitchCmd = &cobra.Command{ + Use: "switch", + Short: "switch to a different profile", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + profilesPath, err := getProfilesPath() + if err != nil { + return err + } + + start, err := stopServiceIfRunning(cmd) + if err != nil { + return err + } + + if err := disconnectClientIfConnected(cmd); err != nil { + return err + } + + if _, err := ensureDefaultProfile(profilesPath, cmd); err != nil { + return err + } + + profilePath := path.Join(profilesPath, args[0]+".json") + if !exists(profilePath) { + return fmt.Errorf("profile %v (%v) does not exist", args[0], profilePath) + } + + if err := os.Remove(configPath); err != nil { + return fmt.Errorf("failed to remove old profile: %v", err) + } + + if err := os.Symlink(profilePath, configPath); err != nil { + return fmt.Errorf("failed to copy new profile %v: %v", args[0], err) + } + + if err := start(); err != nil { + return err + } + + return nil + }, + } +) diff --git a/client/cmd/root.go b/client/cmd/root.go index db02ff5eaad..45b9e7d2fe2 100644 --- a/client/cmd/root.go +++ b/client/cmd/root.go @@ -141,6 +141,7 @@ func init() { rootCmd.AddCommand(sshCmd) rootCmd.AddCommand(routesCmd) rootCmd.AddCommand(debugCmd) + rootCmd.AddCommand(profileCmd) serviceCmd.AddCommand(runCmd, startCmd, stopCmd, restartCmd) // service control commands are subcommands of service serviceCmd.AddCommand(installCmd, uninstallCmd) // service installer commands are subcommands of service @@ -148,6 +149,9 @@ func init() { routesCmd.AddCommand(routesListCmd) routesCmd.AddCommand(routesSelectCmd, routesDeselectCmd) + profileCmd.AddCommand(profileListCmd) + profileCmd.AddCommand(profileSwitchCmd) + debugCmd.AddCommand(debugBundleCmd) debugCmd.AddCommand(logCmd) logCmd.AddCommand(logLevelCmd) From 12bbb0a96ce803fbceb051be404744e0f4a9e0a7 Mon Sep 17 00:00:00 2001 From: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> Date: Thu, 8 Aug 2024 11:29:21 +0200 Subject: [PATCH 2/5] feat: refactor command usage --- client/cmd/profile.go | 147 ++++++++++++++++++++++++++---------------- client/cmd/root.go | 4 +- 2 files changed, 94 insertions(+), 57 deletions(-) diff --git a/client/cmd/profile.go b/client/cmd/profile.go index bfd55a11066..82b51512b6c 100644 --- a/client/cmd/profile.go +++ b/client/cmd/profile.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path" + "path/filepath" "strings" "time" @@ -70,7 +71,7 @@ func exists(path string) bool { return !os.IsNotExist(err) } -func ensureDefaultProfile(profilesPath string, cmd *cobra.Command) (bool, error) { +func ensureDefaultProfile(profilesPath string) (bool, error) { defaultPath := path.Join(profilesPath, "default.json") if !exists(defaultPath) { @@ -99,14 +100,96 @@ func getProfilesPath() (string, error) { return profilesPath, nil } +func switchProfile(cmd *cobra.Command, args []string) error { + profilesPath, err := getProfilesPath() + if err != nil { + return err + } + + if err := disconnectClientIfConnected(cmd); err != nil { + return err + } + + start, err := stopServiceIfRunning(cmd) + if err != nil { + return err + } + + if _, err := ensureDefaultProfile(profilesPath); err != nil { + return err + } + + profilePath := path.Join(profilesPath, args[0]+".json") + if !exists(profilePath) { + return fmt.Errorf("profile %v (%v) does not exist", args[0], profilePath) + } + + if err := os.Remove(configPath); err != nil { + return fmt.Errorf("failed to remove old profile: %v", err) + } + + if err := os.Symlink(profilePath, configPath); err != nil { + return fmt.Errorf("failed to copy new profile %v: %v", args[0], err) + } + + if err := start(); err != nil { + return err + } + + return nil +} + var ( profileCmd = &cobra.Command{ - Use: "profile", - Short: "manages different profiles", + Use: "profile [newProfile]", + Short: "switch to profile newProfile or get the current one", + Args: cobra.MaximumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + if (len(args)) == 1 { + return switchProfile(cmd, args) + } + + realPath, err := filepath.EvalSymlinks(configPath) + if err != nil { + return fmt.Errorf("Couldn't read config at %v", configPath) + } + + // The config is not symlinked, ensure the default profile exists + if realPath == configPath { + cmd.Println("default") + + profilesPath, err := getProfilesPath() + if err != nil { + return err + } + + if err := disconnectClientIfConnected(cmd); err != nil { + return err + } + + start, err := stopServiceIfRunning(cmd) + if err != nil { + return err + } + + if _, err := ensureDefaultProfile(profilesPath); err != nil { + return err + } + + if err := start(); err != nil { + return err + } + } + + profile := strings.TrimSuffix(path.Base(realPath), ".json") + cmd.Println(profile) + + return nil + }, } - profileListCmd = &cobra.Command{ - Use: "list", + profilesCmd = &cobra.Command{ + Use: "profiles", Short: "list all profiles", RunE: func(cmd *cobra.Command, args []string) error { ensureProfileErr := make(chan error) @@ -119,7 +202,7 @@ var ( // Defer this so the profiles are displayed without the service stopping // blocking the thread go func() { - shouldRestart, err := ensureDefaultProfile(profilesPath, cmd) + shouldRestart, err := ensureDefaultProfile(profilesPath) if err != nil { ensureProfileErr <- err @@ -127,13 +210,13 @@ var ( } if shouldRestart { - start, err := stopServiceIfRunning(cmd) - if err != nil { + if err := disconnectClientIfConnected(cmd); err != nil { ensureProfileErr <- err return } - if err := disconnectClientIfConnected(cmd); err != nil { + start, err := stopServiceIfRunning(cmd) + if err != nil { ensureProfileErr <- err return } @@ -159,7 +242,7 @@ var ( continue } - fmt.Println(strings.TrimSuffix(entry.Name(), ".json")) + cmd.Println(strings.TrimSuffix(entry.Name(), ".json")) } if err := <-ensureProfileErr; err != nil { @@ -169,48 +252,4 @@ var ( return nil }, } - - profileSwitchCmd = &cobra.Command{ - Use: "switch", - Short: "switch to a different profile", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - profilesPath, err := getProfilesPath() - if err != nil { - return err - } - - start, err := stopServiceIfRunning(cmd) - if err != nil { - return err - } - - if err := disconnectClientIfConnected(cmd); err != nil { - return err - } - - if _, err := ensureDefaultProfile(profilesPath, cmd); err != nil { - return err - } - - profilePath := path.Join(profilesPath, args[0]+".json") - if !exists(profilePath) { - return fmt.Errorf("profile %v (%v) does not exist", args[0], profilePath) - } - - if err := os.Remove(configPath); err != nil { - return fmt.Errorf("failed to remove old profile: %v", err) - } - - if err := os.Symlink(profilePath, configPath); err != nil { - return fmt.Errorf("failed to copy new profile %v: %v", args[0], err) - } - - if err := start(); err != nil { - return err - } - - return nil - }, - } ) diff --git a/client/cmd/root.go b/client/cmd/root.go index 45b9e7d2fe2..1d6110ca3b4 100644 --- a/client/cmd/root.go +++ b/client/cmd/root.go @@ -142,6 +142,7 @@ func init() { rootCmd.AddCommand(routesCmd) rootCmd.AddCommand(debugCmd) rootCmd.AddCommand(profileCmd) + rootCmd.AddCommand(profilesCmd) serviceCmd.AddCommand(runCmd, startCmd, stopCmd, restartCmd) // service control commands are subcommands of service serviceCmd.AddCommand(installCmd, uninstallCmd) // service installer commands are subcommands of service @@ -149,9 +150,6 @@ func init() { routesCmd.AddCommand(routesListCmd) routesCmd.AddCommand(routesSelectCmd, routesDeselectCmd) - profileCmd.AddCommand(profileListCmd) - profileCmd.AddCommand(profileSwitchCmd) - debugCmd.AddCommand(debugBundleCmd) debugCmd.AddCommand(logCmd) logCmd.AddCommand(logLevelCmd) From 7f1284ec256edcf0ca85efbfc701ff0f80839368 Mon Sep 17 00:00:00 2001 From: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> Date: Thu, 8 Aug 2024 11:57:05 +0200 Subject: [PATCH 3/5] fix: use profiles dir relative to config file --- client/cmd/profile.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/cmd/profile.go b/client/cmd/profile.go index 82b51512b6c..d8d7055e9c2 100644 --- a/client/cmd/profile.go +++ b/client/cmd/profile.go @@ -91,7 +91,8 @@ func ensureDefaultProfile(profilesPath string) (bool, error) { } func getProfilesPath() (string, error) { - profilesPath := path.Join(defaultConfigPathDir, "profiles") + configPathDir := path.Dir(configPath) + profilesPath := path.Join(configPathDir, "profiles") if err := os.MkdirAll(profilesPath, os.ModePerm); err != nil { return "", fmt.Errorf("failed to create profiles directory: %v", err) From 481545e1ab8185d702c032e25ce84328dd7f4a3d Mon Sep 17 00:00:00 2001 From: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> Date: Fri, 9 Aug 2024 22:32:19 +0200 Subject: [PATCH 4/5] feat: move switching logic to daemon --- client/cmd/profile.go | 225 ++------------ client/proto/daemon.pb.go | 535 +++++++++++++++++++++++++++------ client/proto/daemon.proto | 32 +- client/proto/daemon_grpc.pb.go | 114 +++++++ client/proto/generate.sh | 2 +- client/server/profile.go | 162 ++++++++++ client/ui/client_ui.go | 3 + 7 files changed, 784 insertions(+), 289 deletions(-) create mode 100644 client/server/profile.go diff --git a/client/cmd/profile.go b/client/cmd/profile.go index d8d7055e9c2..9b2fffe40ef 100644 --- a/client/cmd/profile.go +++ b/client/cmd/profile.go @@ -1,189 +1,45 @@ package cmd import ( - "context" "fmt" - "os" - "path" - "path/filepath" - "strings" - "time" - "github.com/kardianos/service" "github.com/netbirdio/netbird/client/internal" "github.com/netbirdio/netbird/client/proto" "github.com/spf13/cobra" ) -func stopServiceIfRunning(cmd *cobra.Command) (func() error, error) { - s, err := newSVC(newProgram(context.WithCancel(cmd.Context())), newSVCConfig()) - if err != nil { - return nil, err - } - - status, err := s.Status() - if err != nil { - return nil, err - } - - if status == service.StatusRunning { - if err := s.Stop(); err != nil { - return nil, err - } - - return s.Start, nil - } - - return func() error { return nil }, nil -} - -func disconnectClientIfConnected(cmd *cobra.Command) error { - ctx := internal.CtxInitState(cmd.Context()) - - resp, err := getStatus(ctx) - if err != nil { - return err - } - - if resp.GetStatus() == string(internal.StatusConnected) || resp.GetStatus() == string(internal.StatusConnecting) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*7) - defer cancel() - - conn, err := DialClientGRPCServer(ctx, daemonAddr) - if err != nil { - return fmt.Errorf("failed to connect to service CLI interface %v", err) - } - - defer conn.Close() - - daemonClient := proto.NewDaemonServiceClient(conn) - - if _, err := daemonClient.Down(ctx, &proto.DownRequest{}); err != nil { - return fmt.Errorf("call service down method: %v", err) - } - } - - return nil -} - -func exists(path string) bool { - _, err := os.Stat(path) - return !os.IsNotExist(err) -} - -func ensureDefaultProfile(profilesPath string) (bool, error) { - defaultPath := path.Join(profilesPath, "default.json") - - if !exists(defaultPath) { - if err := os.Rename(configPath, defaultPath); err != nil { - return false, fmt.Errorf("failed to move config.json to default.json: %v", err) - } - - // Create a symlink to the default profile - if err := os.Symlink(defaultPath, configPath); err != nil { - return false, fmt.Errorf("failed to create symlink to default profile: %v", err) - } - - return true, nil - } - - return false, nil -} - -func getProfilesPath() (string, error) { - configPathDir := path.Dir(configPath) - profilesPath := path.Join(configPathDir, "profiles") - - if err := os.MkdirAll(profilesPath, os.ModePerm); err != nil { - return "", fmt.Errorf("failed to create profiles directory: %v", err) - } - - return profilesPath, nil -} - -func switchProfile(cmd *cobra.Command, args []string) error { - profilesPath, err := getProfilesPath() - if err != nil { - return err - } - - if err := disconnectClientIfConnected(cmd); err != nil { - return err - } - - start, err := stopServiceIfRunning(cmd) - if err != nil { - return err - } - - if _, err := ensureDefaultProfile(profilesPath); err != nil { - return err - } - - profilePath := path.Join(profilesPath, args[0]+".json") - if !exists(profilePath) { - return fmt.Errorf("profile %v (%v) does not exist", args[0], profilePath) - } - - if err := os.Remove(configPath); err != nil { - return fmt.Errorf("failed to remove old profile: %v", err) - } - - if err := os.Symlink(profilePath, configPath); err != nil { - return fmt.Errorf("failed to copy new profile %v: %v", args[0], err) - } - - if err := start(); err != nil { - return err - } - - return nil -} - var ( profileCmd = &cobra.Command{ Use: "profile [newProfile]", Short: "switch to profile newProfile or get the current one", Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - if (len(args)) == 1 { - return switchProfile(cmd, args) - } + ctx := internal.CtxInitState(cmd.Context()) - realPath, err := filepath.EvalSymlinks(configPath) + conn, err := DialClientGRPCServer(ctx, daemonAddr) if err != nil { - return fmt.Errorf("Couldn't read config at %v", configPath) + return fmt.Errorf("failed to connect to service CLI interface %v", err) } - // The config is not symlinked, ensure the default profile exists - if realPath == configPath { - cmd.Println("default") + defer conn.Close() - profilesPath, err := getProfilesPath() - if err != nil { - return err - } + daemonClient := proto.NewDaemonServiceClient(conn) - if err := disconnectClientIfConnected(cmd); err != nil { + if (len(args)) == 1 { + if _, err := daemonClient.SwitchProfile(ctx, &proto.SwitchProfileRequest{Profile: args[0]}); err != nil { return err } - start, err := stopServiceIfRunning(cmd) - if err != nil { - return err - } + return nil + } - if _, err := ensureDefaultProfile(profilesPath); err != nil { - return err - } + resp, err := daemonClient.GetProfile(ctx, &proto.GetProfileRequest{}) - if err := start(); err != nil { - return err - } + if err != nil { + return err } - profile := strings.TrimSuffix(path.Base(realPath), ".json") - cmd.Println(profile) + cmd.Println(resp.Profile) return nil }, @@ -193,61 +49,24 @@ var ( Use: "profiles", Short: "list all profiles", RunE: func(cmd *cobra.Command, args []string) error { - ensureProfileErr := make(chan error) + ctx := internal.CtxInitState(cmd.Context()) - profilesPath, err := getProfilesPath() + conn, err := DialClientGRPCServer(ctx, daemonAddr) if err != nil { - return err + return fmt.Errorf("failed to connect to service CLI interface %v", err) } - // Defer this so the profiles are displayed without the service stopping - // blocking the thread - go func() { - shouldRestart, err := ensureDefaultProfile(profilesPath) - - if err != nil { - ensureProfileErr <- err - return - } - - if shouldRestart { - if err := disconnectClientIfConnected(cmd); err != nil { - ensureProfileErr <- err - return - } - - start, err := stopServiceIfRunning(cmd) - if err != nil { - ensureProfileErr <- err - return - } + defer conn.Close() - if err := start(); err != nil { - ensureProfileErr <- err - return - } - } - - ensureProfileErr <- nil - }() - - defer close(ensureProfileErr) + daemonClient := proto.NewDaemonServiceClient(conn) + resp, err := daemonClient.ListProfiles(ctx, &proto.ListProfilesRequest{}) - entries, err := os.ReadDir(profilesPath) if err != nil { - return fmt.Errorf("failed to read profiles directory: %v", err) - } - - for _, entry := range entries { - if entry.IsDir() { - continue - } - - cmd.Println(strings.TrimSuffix(entry.Name(), ".json")) + return err } - if err := <-ensureProfileErr; err != nil { - return err + for _, profile := range resp.Profiles { + cmd.Println(profile) } return nil diff --git a/client/proto/daemon.pb.go b/client/proto/daemon.pb.go index fb10a38d30f..4a995e314fe 100644 --- a/client/proto/daemon.pb.go +++ b/client/proto/daemon.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 -// protoc v4.23.4 +// protoc v5.27.3 // source: daemon.proto package proto @@ -2103,6 +2103,261 @@ func (*SetLogLevelResponse) Descriptor() ([]byte, []int) { return file_daemon_proto_rawDescGZIP(), []int{30} } +type ListProfilesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ListProfilesRequest) Reset() { + *x = ListProfilesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_daemon_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListProfilesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListProfilesRequest) ProtoMessage() {} + +func (x *ListProfilesRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[31] + 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 ListProfilesRequest.ProtoReflect.Descriptor instead. +func (*ListProfilesRequest) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{31} +} + +type ListProfilesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Profiles []string `protobuf:"bytes,1,rep,name=profiles,proto3" json:"profiles,omitempty"` +} + +func (x *ListProfilesResponse) Reset() { + *x = ListProfilesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_daemon_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListProfilesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListProfilesResponse) ProtoMessage() {} + +func (x *ListProfilesResponse) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[32] + 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 ListProfilesResponse.ProtoReflect.Descriptor instead. +func (*ListProfilesResponse) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{32} +} + +func (x *ListProfilesResponse) GetProfiles() []string { + if x != nil { + return x.Profiles + } + return nil +} + +type SwitchProfileRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Profile string `protobuf:"bytes,1,opt,name=profile,proto3" json:"profile,omitempty"` +} + +func (x *SwitchProfileRequest) Reset() { + *x = SwitchProfileRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_daemon_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SwitchProfileRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SwitchProfileRequest) ProtoMessage() {} + +func (x *SwitchProfileRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[33] + 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 SwitchProfileRequest.ProtoReflect.Descriptor instead. +func (*SwitchProfileRequest) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{33} +} + +func (x *SwitchProfileRequest) GetProfile() string { + if x != nil { + return x.Profile + } + return "" +} + +type SwitchProfileResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *SwitchProfileResponse) Reset() { + *x = SwitchProfileResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_daemon_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SwitchProfileResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SwitchProfileResponse) ProtoMessage() {} + +func (x *SwitchProfileResponse) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[34] + 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 SwitchProfileResponse.ProtoReflect.Descriptor instead. +func (*SwitchProfileResponse) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{34} +} + +type GetProfileRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetProfileRequest) Reset() { + *x = GetProfileRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_daemon_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetProfileRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetProfileRequest) ProtoMessage() {} + +func (x *GetProfileRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[35] + 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 GetProfileRequest.ProtoReflect.Descriptor instead. +func (*GetProfileRequest) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{35} +} + +type GetProfileResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Profile string `protobuf:"bytes,1,opt,name=profile,proto3" json:"profile,omitempty"` +} + +func (x *GetProfileResponse) Reset() { + *x = GetProfileResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_daemon_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetProfileResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetProfileResponse) ProtoMessage() {} + +func (x *GetProfileResponse) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[36] + 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 GetProfileResponse.ProtoReflect.Descriptor instead. +func (*GetProfileResponse) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{36} +} + +func (x *GetProfileResponse) GetProfile() string { + if x != nil { + return x.Profile + } + return "" +} + var File_daemon_proto protoreflect.FileDescriptor var file_daemon_proto_rawDesc = []byte{ @@ -2398,66 +2653,94 @@ var file_daemon_proto_rawDesc = []byte{ 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x15, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x62, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, - 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, - 0x05, 0x50, 0x41, 0x4e, 0x49, 0x43, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x41, 0x54, 0x41, - 0x4c, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x08, - 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x04, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, - 0x10, 0x05, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x06, 0x12, 0x09, 0x0a, - 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x07, 0x32, 0xb8, 0x06, 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, 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, - 0x69, 0x6e, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, - 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, - 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, 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, 0x12, 0x45, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, - 0x12, 0x19, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, - 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x64, 0x61, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x32, 0x0a, 0x14, 0x4c, + 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x22, + 0x30, 0x0a, 0x14, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, + 0x65, 0x22, 0x17, 0x0a, 0x15, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x13, 0x0a, 0x11, 0x47, 0x65, + 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, + 0x2e, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2a, + 0x62, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55, + 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x41, 0x4e, 0x49, + 0x43, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x10, 0x02, 0x12, 0x09, + 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, + 0x4e, 0x10, 0x04, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x05, 0x12, 0x09, 0x0a, + 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x06, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, + 0x45, 0x10, 0x07, 0x32, 0x9c, 0x08, 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, 0x4b, 0x0a, + 0x0c, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1b, 0x2e, + 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, + 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, + 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 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, 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, 0x12, 0x45, 0x0a, + 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x53, 0x65, 0x6c, - 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, - 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, - 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x0e, 0x44, 0x65, 0x73, 0x65, 0x6c, 0x65, - 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, - 0x6e, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, - 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x0b, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, 0x75, - 0x6e, 0x64, 0x6c, 0x65, 0x12, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, - 0x62, 0x75, 0x67, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, - 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x48, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, - 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, - 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x64, 0x61, 0x65, - 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x0b, 0x53, 0x65, 0x74, - 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, - 0x6e, 0x2e, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, - 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 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, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, + 0x75, 0x74, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, + 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x4d, 0x0a, 0x0e, 0x44, 0x65, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, + 0x74, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x12, 0x48, 0x0a, 0x0b, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x12, + 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, 0x75, + 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x64, 0x61, + 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x0b, 0x47, 0x65, + 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, + 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, + 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, + 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x74, + 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, + 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x45, + 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x19, 0x2e, 0x64, + 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, + 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x12, 0x4e, 0x0a, 0x0d, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x12, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x77, 0x69, + 0x74, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1d, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x77, 0x69, 0x74, 0x63, + 0x68, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 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 ( @@ -2473,7 +2756,7 @@ func file_daemon_proto_rawDescGZIP() []byte { } var file_daemon_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 32) +var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 38) var file_daemon_proto_goTypes = []interface{}{ (LogLevel)(0), // 0: daemon.LogLevel (*LoginRequest)(nil), // 1: daemon.LoginRequest @@ -2507,16 +2790,22 @@ var file_daemon_proto_goTypes = []interface{}{ (*GetLogLevelResponse)(nil), // 29: daemon.GetLogLevelResponse (*SetLogLevelRequest)(nil), // 30: daemon.SetLogLevelRequest (*SetLogLevelResponse)(nil), // 31: daemon.SetLogLevelResponse - nil, // 32: daemon.Route.ResolvedIPsEntry - (*durationpb.Duration)(nil), // 33: google.protobuf.Duration - (*timestamppb.Timestamp)(nil), // 34: google.protobuf.Timestamp + (*ListProfilesRequest)(nil), // 32: daemon.ListProfilesRequest + (*ListProfilesResponse)(nil), // 33: daemon.ListProfilesResponse + (*SwitchProfileRequest)(nil), // 34: daemon.SwitchProfileRequest + (*SwitchProfileResponse)(nil), // 35: daemon.SwitchProfileResponse + (*GetProfileRequest)(nil), // 36: daemon.GetProfileRequest + (*GetProfileResponse)(nil), // 37: daemon.GetProfileResponse + nil, // 38: daemon.Route.ResolvedIPsEntry + (*durationpb.Duration)(nil), // 39: google.protobuf.Duration + (*timestamppb.Timestamp)(nil), // 40: google.protobuf.Timestamp } var file_daemon_proto_depIdxs = []int32{ - 33, // 0: daemon.LoginRequest.dnsRouteInterval:type_name -> google.protobuf.Duration + 39, // 0: daemon.LoginRequest.dnsRouteInterval:type_name -> google.protobuf.Duration 19, // 1: daemon.StatusResponse.fullStatus:type_name -> daemon.FullStatus - 34, // 2: daemon.PeerState.connStatusUpdate:type_name -> google.protobuf.Timestamp - 34, // 3: daemon.PeerState.lastWireguardHandshake:type_name -> google.protobuf.Timestamp - 33, // 4: daemon.PeerState.latency:type_name -> google.protobuf.Duration + 40, // 2: daemon.PeerState.connStatusUpdate:type_name -> google.protobuf.Timestamp + 40, // 3: daemon.PeerState.lastWireguardHandshake:type_name -> google.protobuf.Timestamp + 39, // 4: daemon.PeerState.latency:type_name -> google.protobuf.Duration 16, // 5: daemon.FullStatus.managementState:type_name -> daemon.ManagementState 15, // 6: daemon.FullStatus.signalState:type_name -> daemon.SignalState 14, // 7: daemon.FullStatus.localPeerState:type_name -> daemon.LocalPeerState @@ -2524,7 +2813,7 @@ var file_daemon_proto_depIdxs = []int32{ 17, // 9: daemon.FullStatus.relays:type_name -> daemon.RelayState 18, // 10: daemon.FullStatus.dns_servers:type_name -> daemon.NSGroupState 25, // 11: daemon.ListRoutesResponse.routes:type_name -> daemon.Route - 32, // 12: daemon.Route.resolvedIPs:type_name -> daemon.Route.ResolvedIPsEntry + 38, // 12: daemon.Route.resolvedIPs:type_name -> daemon.Route.ResolvedIPsEntry 0, // 13: daemon.GetLogLevelResponse.level:type_name -> daemon.LogLevel 0, // 14: daemon.SetLogLevelRequest.level:type_name -> daemon.LogLevel 24, // 15: daemon.Route.ResolvedIPsEntry.value:type_name -> daemon.IPList @@ -2540,20 +2829,26 @@ var file_daemon_proto_depIdxs = []int32{ 26, // 25: daemon.DaemonService.DebugBundle:input_type -> daemon.DebugBundleRequest 28, // 26: daemon.DaemonService.GetLogLevel:input_type -> daemon.GetLogLevelRequest 30, // 27: daemon.DaemonService.SetLogLevel:input_type -> daemon.SetLogLevelRequest - 2, // 28: daemon.DaemonService.Login:output_type -> daemon.LoginResponse - 4, // 29: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse - 6, // 30: daemon.DaemonService.Up:output_type -> daemon.UpResponse - 8, // 31: daemon.DaemonService.Status:output_type -> daemon.StatusResponse - 10, // 32: daemon.DaemonService.Down:output_type -> daemon.DownResponse - 12, // 33: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse - 21, // 34: daemon.DaemonService.ListRoutes:output_type -> daemon.ListRoutesResponse - 23, // 35: daemon.DaemonService.SelectRoutes:output_type -> daemon.SelectRoutesResponse - 23, // 36: daemon.DaemonService.DeselectRoutes:output_type -> daemon.SelectRoutesResponse - 27, // 37: daemon.DaemonService.DebugBundle:output_type -> daemon.DebugBundleResponse - 29, // 38: daemon.DaemonService.GetLogLevel:output_type -> daemon.GetLogLevelResponse - 31, // 39: daemon.DaemonService.SetLogLevel:output_type -> daemon.SetLogLevelResponse - 28, // [28:40] is the sub-list for method output_type - 16, // [16:28] is the sub-list for method input_type + 36, // 28: daemon.DaemonService.GetProfile:input_type -> daemon.GetProfileRequest + 32, // 29: daemon.DaemonService.ListProfiles:input_type -> daemon.ListProfilesRequest + 34, // 30: daemon.DaemonService.SwitchProfile:input_type -> daemon.SwitchProfileRequest + 2, // 31: daemon.DaemonService.Login:output_type -> daemon.LoginResponse + 4, // 32: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse + 6, // 33: daemon.DaemonService.Up:output_type -> daemon.UpResponse + 8, // 34: daemon.DaemonService.Status:output_type -> daemon.StatusResponse + 10, // 35: daemon.DaemonService.Down:output_type -> daemon.DownResponse + 12, // 36: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse + 21, // 37: daemon.DaemonService.ListRoutes:output_type -> daemon.ListRoutesResponse + 23, // 38: daemon.DaemonService.SelectRoutes:output_type -> daemon.SelectRoutesResponse + 23, // 39: daemon.DaemonService.DeselectRoutes:output_type -> daemon.SelectRoutesResponse + 27, // 40: daemon.DaemonService.DebugBundle:output_type -> daemon.DebugBundleResponse + 29, // 41: daemon.DaemonService.GetLogLevel:output_type -> daemon.GetLogLevelResponse + 31, // 42: daemon.DaemonService.SetLogLevel:output_type -> daemon.SetLogLevelResponse + 37, // 43: daemon.DaemonService.GetProfile:output_type -> daemon.GetProfileResponse + 33, // 44: daemon.DaemonService.ListProfiles:output_type -> daemon.ListProfilesResponse + 35, // 45: daemon.DaemonService.SwitchProfile:output_type -> daemon.SwitchProfileResponse + 31, // [31:46] is the sub-list for method output_type + 16, // [16:31] is the sub-list for method input_type 16, // [16:16] is the sub-list for extension type_name 16, // [16:16] is the sub-list for extension extendee 0, // [0:16] is the sub-list for field type_name @@ -2937,6 +3232,78 @@ func file_daemon_proto_init() { return nil } } + file_daemon_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListProfilesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_daemon_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListProfilesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_daemon_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SwitchProfileRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_daemon_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SwitchProfileResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_daemon_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetProfileRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_daemon_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetProfileResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_daemon_proto_msgTypes[0].OneofWrappers = []interface{}{} type x struct{} @@ -2945,7 +3312,7 @@ func file_daemon_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_daemon_proto_rawDesc, NumEnums: 1, - NumMessages: 32, + NumMessages: 38, NumExtensions: 0, NumServices: 1, }, diff --git a/client/proto/daemon.proto b/client/proto/daemon.proto index 43c379fb51e..0ea84aec426 100644 --- a/client/proto/daemon.proto +++ b/client/proto/daemon.proto @@ -45,6 +45,15 @@ service DaemonService { // SetLogLevel sets the log level of the daemon rpc SetLogLevel(SetLogLevelRequest) returns (SetLogLevelResponse) {} + + // GetProfile returns the current profile + rpc GetProfile(GetProfileRequest) returns (GetProfileResponse) {} + + // ListProfiles lists all available profiles + rpc ListProfiles(ListProfilesRequest) returns (ListProfilesResponse) {} + + // SwitchProfile switches to a different profile (network) + rpc SwitchProfile(SwitchProfileRequest) returns (SwitchProfileResponse) {} }; message LoginRequest { @@ -293,4 +302,25 @@ message SetLogLevelRequest { } message SetLogLevelResponse { -} \ No newline at end of file +} + +message ListProfilesRequest { +} + +message ListProfilesResponse { + repeated string profiles = 1; +} + +message SwitchProfileRequest { + string profile = 1; +} + +message SwitchProfileResponse { +} + +message GetProfileRequest { +} + +message GetProfileResponse { + string profile = 1; +} diff --git a/client/proto/daemon_grpc.pb.go b/client/proto/daemon_grpc.pb.go index e0bc117e52d..90b903d97b4 100644 --- a/client/proto/daemon_grpc.pb.go +++ b/client/proto/daemon_grpc.pb.go @@ -43,6 +43,12 @@ type DaemonServiceClient interface { GetLogLevel(ctx context.Context, in *GetLogLevelRequest, opts ...grpc.CallOption) (*GetLogLevelResponse, error) // SetLogLevel sets the log level of the daemon SetLogLevel(ctx context.Context, in *SetLogLevelRequest, opts ...grpc.CallOption) (*SetLogLevelResponse, error) + // GetProfile returns the current profile + GetProfile(ctx context.Context, in *GetProfileRequest, opts ...grpc.CallOption) (*GetProfileResponse, error) + // ListProfiles lists all available profiles + ListProfiles(ctx context.Context, in *ListProfilesRequest, opts ...grpc.CallOption) (*ListProfilesResponse, error) + // SwitchProfile switches to a different profile (network) + SwitchProfile(ctx context.Context, in *SwitchProfileRequest, opts ...grpc.CallOption) (*SwitchProfileResponse, error) } type daemonServiceClient struct { @@ -161,6 +167,33 @@ func (c *daemonServiceClient) SetLogLevel(ctx context.Context, in *SetLogLevelRe return out, nil } +func (c *daemonServiceClient) GetProfile(ctx context.Context, in *GetProfileRequest, opts ...grpc.CallOption) (*GetProfileResponse, error) { + out := new(GetProfileResponse) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/GetProfile", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *daemonServiceClient) ListProfiles(ctx context.Context, in *ListProfilesRequest, opts ...grpc.CallOption) (*ListProfilesResponse, error) { + out := new(ListProfilesResponse) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/ListProfiles", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *daemonServiceClient) SwitchProfile(ctx context.Context, in *SwitchProfileRequest, opts ...grpc.CallOption) (*SwitchProfileResponse, error) { + out := new(SwitchProfileResponse) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/SwitchProfile", 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 @@ -190,6 +223,12 @@ type DaemonServiceServer interface { GetLogLevel(context.Context, *GetLogLevelRequest) (*GetLogLevelResponse, error) // SetLogLevel sets the log level of the daemon SetLogLevel(context.Context, *SetLogLevelRequest) (*SetLogLevelResponse, error) + // GetProfile returns the current profile + GetProfile(context.Context, *GetProfileRequest) (*GetProfileResponse, error) + // ListProfiles lists all available profiles + ListProfiles(context.Context, *ListProfilesRequest) (*ListProfilesResponse, error) + // SwitchProfile switches to a different profile (network) + SwitchProfile(context.Context, *SwitchProfileRequest) (*SwitchProfileResponse, error) mustEmbedUnimplementedDaemonServiceServer() } @@ -233,6 +272,15 @@ func (UnimplementedDaemonServiceServer) GetLogLevel(context.Context, *GetLogLeve func (UnimplementedDaemonServiceServer) SetLogLevel(context.Context, *SetLogLevelRequest) (*SetLogLevelResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SetLogLevel not implemented") } +func (UnimplementedDaemonServiceServer) GetProfile(context.Context, *GetProfileRequest) (*GetProfileResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetProfile not implemented") +} +func (UnimplementedDaemonServiceServer) ListProfiles(context.Context, *ListProfilesRequest) (*ListProfilesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListProfiles not implemented") +} +func (UnimplementedDaemonServiceServer) SwitchProfile(context.Context, *SwitchProfileRequest) (*SwitchProfileResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SwitchProfile not implemented") +} func (UnimplementedDaemonServiceServer) mustEmbedUnimplementedDaemonServiceServer() {} // UnsafeDaemonServiceServer may be embedded to opt out of forward compatibility for this service. @@ -462,6 +510,60 @@ func _DaemonService_SetLogLevel_Handler(srv interface{}, ctx context.Context, de return interceptor(ctx, in, info, handler) } +func _DaemonService_GetProfile_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetProfileRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DaemonServiceServer).GetProfile(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/daemon.DaemonService/GetProfile", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DaemonServiceServer).GetProfile(ctx, req.(*GetProfileRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _DaemonService_ListProfiles_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListProfilesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DaemonServiceServer).ListProfiles(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/daemon.DaemonService/ListProfiles", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DaemonServiceServer).ListProfiles(ctx, req.(*ListProfilesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _DaemonService_SwitchProfile_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SwitchProfileRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DaemonServiceServer).SwitchProfile(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/daemon.DaemonService/SwitchProfile", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DaemonServiceServer).SwitchProfile(ctx, req.(*SwitchProfileRequest)) + } + 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) @@ -517,6 +619,18 @@ var DaemonService_ServiceDesc = grpc.ServiceDesc{ MethodName: "SetLogLevel", Handler: _DaemonService_SetLogLevel_Handler, }, + { + MethodName: "GetProfile", + Handler: _DaemonService_GetProfile_Handler, + }, + { + MethodName: "ListProfiles", + Handler: _DaemonService_ListProfiles_Handler, + }, + { + MethodName: "SwitchProfile", + Handler: _DaemonService_SwitchProfile_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "daemon.proto", diff --git a/client/proto/generate.sh b/client/proto/generate.sh index 52fe23d7f1f..a592cf664ad 100755 --- a/client/proto/generate.sh +++ b/client/proto/generate.sh @@ -14,4 +14,4 @@ cd "$script_path" go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1 protoc -I ./ ./daemon.proto --go_out=../ --go-grpc_out=../ --experimental_allow_proto3_optional -cd "$old_pwd" \ No newline at end of file +cd "$old_pwd" diff --git a/client/server/profile.go b/client/server/profile.go new file mode 100644 index 00000000000..cf4a1b9fcb6 --- /dev/null +++ b/client/server/profile.go @@ -0,0 +1,162 @@ +package server + +import ( + "context" + "fmt" + "os" + "path" + "path/filepath" + "strings" + "time" + + log "github.com/sirupsen/logrus" + + "github.com/netbirdio/netbird/client/internal" + "github.com/netbirdio/netbird/client/proto" +) + +// EnsureDefaultProfile make the config a symlink to the profiles/default.json should it not exists, +// making the current config the default profile +// Cancelling the context passed will reconnect the client +// Can be cancelled directly after EnsureDefaultProfile, given that no further changes to the profile are required +func EnsureDefaultProfile(s *Server, ctx context.Context) error { + configPath := s.latestConfigInput.ConfigPath + profilesPath := path.Join(path.Dir(configPath), "profiles") + defaultPath := path.Join(profilesPath, "default.json") + + if err := os.MkdirAll(profilesPath, os.ModePerm); err != nil { + return fmt.Errorf("failed to create profiles directory: %v", err) + } + + log.Debugln("ensuring default profile exists") + + resp, err := s.Status(ctx, &proto.StatusRequest{GetFullPeerStatus: false}) + if err != nil { + return err + } + + if resp.GetStatus() == string(internal.StatusConnected) || + resp.GetStatus() == string(internal.StatusConnecting) { + log.Debugln("client is connected, disconnecting") + + go func() { + // TODO: Handle these errors, somehow + res, err := s.Down(ctx, &proto.DownRequest{}) + log.Debugf("disconnected client due to profile switch: %v %v", res, err) + + time.Sleep(7 * time.Second) + + select { + case <-ctx.Done(): + _, _ = s.Up(ctx, &proto.UpRequest{}) + log.Debugln("reconnecting client after profile switch") + } + }() + } + + if _, err := os.Stat(defaultPath); os.IsNotExist(err) { + if err := os.Rename(configPath, defaultPath); err != nil { + return fmt.Errorf("failed to move config.json to default.json: %v", err) + } + + // Create a symlink to the default profile + if err := os.Symlink(defaultPath, configPath); err != nil { + return fmt.Errorf("failed to create symlink to default profile: %v", err) + } + } + + return nil +} + +func (s *Server) GetProfile(ctx context.Context, req *proto.GetProfileRequest) (*proto.GetProfileResponse, error) { + profile := "default" + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + if err := EnsureDefaultProfile(s, ctx); err != nil { + return nil, err + } + + configPath := s.latestConfigInput.ConfigPath + + realPath, err := filepath.EvalSymlinks(configPath) + if err != nil { + return nil, fmt.Errorf("Couldn't read config at %v", configPath) + } + + if realPath != configPath { + profile = strings.TrimSuffix(path.Base(realPath), ".json") + } + + return &proto.GetProfileResponse{Profile: profile}, nil +} + +// ListProfiles lists all available profiles +func (s *Server) ListProfiles(ctx context.Context, req *proto.ListProfilesRequest) (*proto.ListProfilesResponse, error) { + profiles := []string{} + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + if err := EnsureDefaultProfile(s, ctx); err != nil { + return nil, err + } + + configPath := s.latestConfigInput.ConfigPath + profilesPath := path.Join(path.Dir(configPath), "profiles") + + entries, err := os.ReadDir(profilesPath) + if err != nil { + return nil, fmt.Errorf("failed to read profiles directory: %v", err) + } + + for _, entry := range entries { + if entry.IsDir() { + continue + } + + name := strings.TrimSuffix(entry.Name(), ".json") + profiles = append(profiles, name) + } + + return &proto.ListProfilesResponse{Profiles: profiles}, nil +} + +// SwitchProfile switches to a different profile (network) +func (s *Server) SwitchProfile(ctx context.Context, req *proto.SwitchProfileRequest) (*proto.SwitchProfileResponse, error) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + if err := EnsureDefaultProfile(s, ctx); err != nil { + return nil, err + } + + configPath := s.latestConfigInput.ConfigPath + profilePath := path.Join(path.Join(path.Dir(configPath), "profiles"), req.Profile+".json") + + log.Debugf("replacing config at %v with config at %v", configPath, profilePath) + + if _, err := os.Stat(profilePath); os.IsNotExist(err) { + return nil, fmt.Errorf("profile %v (%v) does not exist", req.Profile, profilePath) + } + + if err := os.Remove(configPath); err != nil { + return nil, fmt.Errorf("failed to remove old profile: %v", err) + } + + if err := os.Symlink(profilePath, configPath); err != nil { + return nil, fmt.Errorf("failed to copy new profile %v: %v", req.Profile, err) + } + + config, err := internal.UpdateOrCreateConfig(internal.ConfigInput{ConfigPath: configPath}) + if err != nil { + return nil, err + } + + s.mutex.Lock() + s.config = config + s.mutex.Unlock() + + return &proto.SwitchProfileResponse{}, nil +} diff --git a/client/ui/client_ui.go b/client/ui/client_ui.go index 58004dd4a5d..06c9cc779a3 100644 --- a/client/ui/client_ui.go +++ b/client/ui/client_ui.go @@ -163,6 +163,7 @@ type serviceClient struct { mAutoConnect *systray.MenuItem mEnableRosenpass *systray.MenuItem mAdvancedSettings *systray.MenuItem + mProfile *systray.MenuItem // application with main windows. app fyne.App @@ -542,6 +543,8 @@ func (s *serviceClient) onTrayReady() { s.mAdminPanel = systray.AddMenuItem("Admin Panel", "Netbird Admin Panel") systray.AddSeparator() + s.mProfile = systray.AddMenuItem("Profile", "Profile to connect to") + s.mSettings = systray.AddMenuItem("Settings", "Settings of the application") s.mAllowSSH = s.mSettings.AddSubMenuItemCheckbox("Allow SSH", "Allow SSH connections", false) s.mAutoConnect = s.mSettings.AddSubMenuItemCheckbox("Connect on Startup", "Connect automatically when the service starts", false) From 936dac8e6c7c0e955ee628ed9d3013521b95c9bf Mon Sep 17 00:00:00 2001 From: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> Date: Thu, 29 Aug 2024 15:30:21 +0200 Subject: [PATCH 5/5] feat: --profile flags for up and login commands --- client/cmd/login.go | 23 +++++ client/cmd/profile.go | 33 +++++- client/cmd/root.go | 2 + client/cmd/up.go | 20 ++++ client/proto/daemon.pb.go | 212 ++++++++++++++++++++++---------------- client/proto/daemon.proto | 4 + client/server/profile.go | 203 +++++++++++++++++++++++------------- client/server/server.go | 9 ++ 8 files changed, 343 insertions(+), 163 deletions(-) diff --git a/client/cmd/login.go b/client/cmd/login.go index 14c973d916a..00d5c84bd67 100644 --- a/client/cmd/login.go +++ b/client/cmd/login.go @@ -80,6 +80,25 @@ var loginCmd = &cobra.Command{ client := proto.NewDaemonServiceClient(conn) + if profileName != "" { + profilesDir, err := getUserProfilesDir() + if err != nil { + return err + } + + usesSetupKey := setupKey != "" + + _, err = client.SwitchProfile(ctx, &proto.SwitchProfileRequest{ + Profile: profileName, + UserProfilesPath: profilesDir, + IsNewSystemProfile: &usesSetupKey, + }) + + if err != nil { + return err + } + } + loginRequest := proto.LoginRequest{ SetupKey: setupKey, ManagementUrl: managementURL, @@ -130,6 +149,10 @@ var loginCmd = &cobra.Command{ }, } +func init() { + logCmd.PersistentFlags().StringVar(&profileName, profileNameFlag, "", "the profile to use") +} + func foregroundLogin(ctx context.Context, cmd *cobra.Command, config *internal.Config, setupKey string) error { needsLogin := false diff --git a/client/cmd/profile.go b/client/cmd/profile.go index 9b2fffe40ef..1686c024d80 100644 --- a/client/cmd/profile.go +++ b/client/cmd/profile.go @@ -2,12 +2,29 @@ package cmd import ( "fmt" + "os" + "path" "github.com/netbirdio/netbird/client/internal" "github.com/netbirdio/netbird/client/proto" "github.com/spf13/cobra" ) +func getUserProfilesDir() (string, error) { + config, err := os.UserConfigDir() + if err != nil { + return "", err + } + + profilesDir := path.Join(config, "netbird", "profiles") + + if err := os.MkdirAll(profilesDir, os.ModeDir); err != nil { + return "", err + } + + return profilesDir, nil +} + var ( profileCmd = &cobra.Command{ Use: "profile [newProfile]", @@ -25,8 +42,13 @@ var ( daemonClient := proto.NewDaemonServiceClient(conn) - if (len(args)) == 1 { - if _, err := daemonClient.SwitchProfile(ctx, &proto.SwitchProfileRequest{Profile: args[0]}); err != nil { + profilesDir, err := getUserProfilesDir() + if err != nil { + return err + } + + if len(args) == 1 { + if _, err := daemonClient.SwitchProfile(ctx, &proto.SwitchProfileRequest{Profile: args[0], UserProfilesPath: profilesDir}); err != nil { return err } @@ -56,10 +78,15 @@ var ( return fmt.Errorf("failed to connect to service CLI interface %v", err) } + profilesDir, err := getUserProfilesDir() + if err != nil { + return err + } + defer conn.Close() daemonClient := proto.NewDaemonServiceClient(conn) - resp, err := daemonClient.ListProfiles(ctx, &proto.ListProfilesRequest{}) + resp, err := daemonClient.ListProfiles(ctx, &proto.ListProfilesRequest{UserProfilesPath: profilesDir}) if err != nil { return err diff --git a/client/cmd/root.go b/client/cmd/root.go index 1d6110ca3b4..6f151c702fe 100644 --- a/client/cmd/root.go +++ b/client/cmd/root.go @@ -38,6 +38,7 @@ const ( extraIFaceBlackListFlag = "extra-iface-blacklist" dnsRouteIntervalFlag = "dns-router-interval" systemInfoFlag = "system-info" + profileNameFlag = "profile" ) var ( @@ -72,6 +73,7 @@ var ( anonymizeFlag bool debugSystemInfoFlag bool dnsRouteInterval time.Duration + profileName string rootCmd = &cobra.Command{ Use: "netbird", diff --git a/client/cmd/up.go b/client/cmd/up.go index f69e9eb2749..6f189e60473 100644 --- a/client/cmd/up.go +++ b/client/cmd/up.go @@ -48,6 +48,7 @@ func init() { ) upCmd.PersistentFlags().StringSliceVar(&extraIFaceBlackList, extraIFaceBlackListFlag, nil, "Extra list of default interfaces to ignore for listening") upCmd.PersistentFlags().DurationVar(&dnsRouteInterval, dnsRouteIntervalFlag, time.Minute, "DNS route update interval") + upCmd.PersistentFlags().StringVar(&profileName, profileNameFlag, "", "the profile to use") } func upFunc(cmd *cobra.Command, args []string) error { @@ -189,6 +190,25 @@ func runInDaemonMode(ctx context.Context, cmd *cobra.Command) error { client := proto.NewDaemonServiceClient(conn) + if profileName != "" { + profilesDir, err := getUserProfilesDir() + if err != nil { + return err + } + + usesSetupKey := setupKey != "" + + _, err = client.SwitchProfile(ctx, &proto.SwitchProfileRequest{ + Profile: profileName, + UserProfilesPath: profilesDir, + IsNewSystemProfile: &usesSetupKey, + }) + + if err != nil { + return err + } + } + status, err := client.Status(ctx, &proto.StatusRequest{}) if err != nil { return fmt.Errorf("unable to get daemon status: %v", err) diff --git a/client/proto/daemon.pb.go b/client/proto/daemon.pb.go index 4a995e314fe..f16b499f672 100644 --- a/client/proto/daemon.pb.go +++ b/client/proto/daemon.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 -// protoc v5.27.3 +// protoc v4.25.3 // source: daemon.proto package proto @@ -2107,6 +2107,8 @@ type ListProfilesRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + + UserProfilesPath string `protobuf:"bytes,1,opt,name=userProfilesPath,proto3" json:"userProfilesPath,omitempty"` } func (x *ListProfilesRequest) Reset() { @@ -2141,6 +2143,13 @@ func (*ListProfilesRequest) Descriptor() ([]byte, []int) { return file_daemon_proto_rawDescGZIP(), []int{31} } +func (x *ListProfilesRequest) GetUserProfilesPath() string { + if x != nil { + return x.UserProfilesPath + } + return "" +} + type ListProfilesResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2193,7 +2202,9 @@ type SwitchProfileRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Profile string `protobuf:"bytes,1,opt,name=profile,proto3" json:"profile,omitempty"` + Profile string `protobuf:"bytes,1,opt,name=profile,proto3" json:"profile,omitempty"` + UserProfilesPath string `protobuf:"bytes,2,opt,name=userProfilesPath,proto3" json:"userProfilesPath,omitempty"` + IsNewSystemProfile *bool `protobuf:"varint,3,opt,name=isNewSystemProfile,proto3,oneof" json:"isNewSystemProfile,omitempty"` } func (x *SwitchProfileRequest) Reset() { @@ -2235,6 +2246,20 @@ func (x *SwitchProfileRequest) GetProfile() string { return "" } +func (x *SwitchProfileRequest) GetUserProfilesPath() string { + if x != nil { + return x.UserProfilesPath + } + return "" +} + +func (x *SwitchProfileRequest) GetIsNewSystemProfile() bool { + if x != nil && x.IsNewSystemProfile != nil { + return *x.IsNewSystemProfile + } + return false +} + type SwitchProfileResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2653,94 +2678,104 @@ var file_daemon_proto_rawDesc = []byte{ 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x15, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, - 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x32, 0x0a, 0x14, 0x4c, - 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x22, - 0x30, 0x0a, 0x14, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, - 0x65, 0x22, 0x17, 0x0a, 0x15, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x13, 0x0a, 0x11, 0x47, 0x65, - 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, - 0x2e, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2a, - 0x62, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55, - 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x41, 0x4e, 0x49, - 0x43, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x10, 0x02, 0x12, 0x09, - 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, - 0x4e, 0x10, 0x04, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x05, 0x12, 0x09, 0x0a, - 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x06, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, - 0x45, 0x10, 0x07, 0x32, 0x9c, 0x08, 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, 0x4b, 0x0a, - 0x0c, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1b, 0x2e, - 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, - 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, - 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 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, 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, 0x12, 0x45, 0x0a, - 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x64, 0x61, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x41, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x10, 0x75, + 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x50, 0x61, 0x74, 0x68, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x75, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x73, 0x50, 0x61, 0x74, 0x68, 0x22, 0x32, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x50, + 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x22, 0xa8, 0x01, 0x0a, 0x14, + 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x2a, + 0x0a, 0x10, 0x75, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x50, 0x61, + 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x75, 0x73, 0x65, 0x72, 0x50, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x50, 0x61, 0x74, 0x68, 0x12, 0x33, 0x0a, 0x12, 0x69, 0x73, + 0x4e, 0x65, 0x77, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x12, 0x69, 0x73, 0x4e, 0x65, 0x77, 0x53, + 0x79, 0x73, 0x74, 0x65, 0x6d, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x88, 0x01, 0x01, 0x42, + 0x15, 0x0a, 0x13, 0x5f, 0x69, 0x73, 0x4e, 0x65, 0x77, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x50, + 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x17, 0x0a, 0x15, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, + 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x13, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x22, 0x2e, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x2a, 0x62, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, + 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, + 0x05, 0x50, 0x41, 0x4e, 0x49, 0x43, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x41, 0x54, 0x41, + 0x4c, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x08, + 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x04, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, + 0x10, 0x05, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x06, 0x12, 0x09, 0x0a, + 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x07, 0x32, 0x9c, 0x08, 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, 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, + 0x69, 0x6e, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, + 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, + 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, 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, 0x12, 0x45, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, + 0x12, 0x19, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, + 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, - 0x75, 0x74, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, - 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x4d, 0x0a, 0x0e, 0x44, 0x65, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, - 0x74, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x6c, - 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, - 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x48, 0x0a, 0x0b, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x12, - 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, 0x75, - 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x64, 0x61, - 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x0b, 0x47, 0x65, - 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, - 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, - 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, - 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x74, - 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, - 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x45, - 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x19, 0x2e, 0x64, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x53, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, + 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, + 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x0e, 0x44, 0x65, 0x73, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, + 0x6e, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, + 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x0b, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, 0x75, + 0x6e, 0x64, 0x6c, 0x65, 0x12, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, + 0x62, 0x75, 0x67, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, + 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x48, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, + 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, + 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x64, 0x61, 0x65, + 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x0b, 0x53, 0x65, 0x74, + 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, + 0x6e, 0x2e, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, + 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0x45, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, + 0x65, 0x12, 0x19, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, - 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, - 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x4e, 0x0a, 0x0d, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x66, - 0x69, 0x6c, 0x65, 0x12, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x77, 0x69, - 0x74, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1d, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x77, 0x69, 0x74, 0x63, - 0x68, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 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, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x4c, 0x69, + 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, + 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4e, 0x0a, 0x0d, 0x53, 0x77, 0x69, 0x74, 0x63, + 0x68, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, + 0x6e, 0x2e, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, + 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 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 ( @@ -3306,6 +3341,7 @@ func file_daemon_proto_init() { } } file_daemon_proto_msgTypes[0].OneofWrappers = []interface{}{} + file_daemon_proto_msgTypes[33].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/client/proto/daemon.proto b/client/proto/daemon.proto index 0ea84aec426..d480e3301f9 100644 --- a/client/proto/daemon.proto +++ b/client/proto/daemon.proto @@ -305,6 +305,7 @@ message SetLogLevelResponse { } message ListProfilesRequest { + string userProfilesPath = 1; } message ListProfilesResponse { @@ -313,6 +314,9 @@ message ListProfilesResponse { message SwitchProfileRequest { string profile = 1; + string userProfilesPath = 2; + + optional bool isNewSystemProfile = 3; } message SwitchProfileResponse { diff --git a/client/server/profile.go b/client/server/profile.go index cf4a1b9fcb6..ff5d0a29aa6 100644 --- a/client/server/profile.go +++ b/client/server/profile.go @@ -3,11 +3,10 @@ package server import ( "context" "fmt" + "io/fs" "os" "path" - "path/filepath" "strings" - "time" log "github.com/sirupsen/logrus" @@ -15,21 +14,68 @@ import ( "github.com/netbirdio/netbird/client/proto" ) -// EnsureDefaultProfile make the config a symlink to the profiles/default.json should it not exists, -// making the current config the default profile -// Cancelling the context passed will reconnect the client -// Can be cancelled directly after EnsureDefaultProfile, given that no further changes to the profile are required -func EnsureDefaultProfile(s *Server, ctx context.Context) error { - configPath := s.latestConfigInput.ConfigPath - profilesPath := path.Join(path.Dir(configPath), "profiles") - defaultPath := path.Join(profilesPath, "default.json") +const ( + persistentProfilePath = "/etc/netbird/.profile" + defaultProfilePath = "/etc/netbird/config.json" + systemProfilesPath = "/etc/netbird/profiles" +) + +type Profile struct { + Name string + Path string +} + +type ProfileState struct { + currentProfile Profile +} + +func (p *ProfileState) WritePersistantProfile(profile *Profile) error { + return os.WriteFile(persistentProfilePath, []byte(profile.Name+"\n"+profile.Path), os.ModePerm) +} + +func (p *ProfileState) GetPersistantProfile() (*Profile, error) { + if _, err := os.Stat(persistentProfilePath); os.IsNotExist(err) { + return nil, err + } + + profileBytes, err := os.ReadFile(persistentProfilePath) + if err != nil { + return nil, err + } + + profile := strings.SplitN(string(profileBytes), "\n", 2) + + return &Profile{ + Name: profile[0], + Path: profile[1], + }, nil +} - if err := os.MkdirAll(profilesPath, os.ModePerm); err != nil { - return fmt.Errorf("failed to create profiles directory: %v", err) +func (p *ProfileState) Init() error { + currentProfile, err := p.GetPersistantProfile() + defaultProfile := Profile{"default", defaultProfilePath} + + if err != nil { + if !os.IsNotExist(err) { + return err + } + + log.Debugln("Current profile couldn't be read due to .profile not existing, using default") + + if err = p.WritePersistantProfile(&defaultProfile); err != nil { + return err + } + + p.currentProfile = defaultProfile + return nil } - log.Debugln("ensuring default profile exists") + p.currentProfile = *currentProfile + return nil +} +// reconnectAfter disconnects the client until the passed context is cancelled +func reconnectAfter(s *Server, ctx context.Context) error { resp, err := s.Status(ctx, &proto.StatusRequest{GetFullPeerStatus: false}) if err != nil { return err @@ -44,83 +90,99 @@ func EnsureDefaultProfile(s *Server, ctx context.Context) error { res, err := s.Down(ctx, &proto.DownRequest{}) log.Debugf("disconnected client due to profile switch: %v %v", res, err) - time.Sleep(7 * time.Second) + <-ctx.Done() - select { - case <-ctx.Done(): - _, _ = s.Up(ctx, &proto.UpRequest{}) - log.Debugln("reconnecting client after profile switch") - } + _, _ = s.Up(ctx, &proto.UpRequest{}) + log.Debugln("reconnecting client after profile switch") }() } - if _, err := os.Stat(defaultPath); os.IsNotExist(err) { - if err := os.Rename(configPath, defaultPath); err != nil { - return fmt.Errorf("failed to move config.json to default.json: %v", err) - } - - // Create a symlink to the default profile - if err := os.Symlink(defaultPath, configPath); err != nil { - return fmt.Errorf("failed to create symlink to default profile: %v", err) - } - } - return nil } func (s *Server) GetProfile(ctx context.Context, req *proto.GetProfileRequest) (*proto.GetProfileResponse, error) { - profile := "default" + return &proto.GetProfileResponse{Profile: s.profileState.currentProfile.Name}, nil +} - ctx, cancel := context.WithCancel(ctx) - defer cancel() +func getProfiles(userProfilesPath string) ([]Profile, error) { + profiles := []Profile{{"default", defaultProfilePath}} - if err := EnsureDefaultProfile(s, ctx); err != nil { + userProfiles, err := os.ReadDir(userProfilesPath) + if err != nil { return nil, err } - configPath := s.latestConfigInput.ConfigPath - - realPath, err := filepath.EvalSymlinks(configPath) + systemProfiles, err := os.ReadDir(systemProfilesPath) if err != nil { - return nil, fmt.Errorf("Couldn't read config at %v", configPath) + return nil, err } - if realPath != configPath { - profile = strings.TrimSuffix(path.Base(realPath), ".json") + processProfiles := func(entries []fs.DirEntry) { + for _, entry := range entries { + if entry.IsDir() { + continue + } + + profiles = append(profiles, Profile{ + Name: strings.TrimSuffix(entry.Name(), ".json"), + Path: path.Join(userProfilesPath, entry.Name()), + }) + } } - return &proto.GetProfileResponse{Profile: profile}, nil + processProfiles(userProfiles) + processProfiles(systemProfiles) + + return profiles, nil } // ListProfiles lists all available profiles func (s *Server) ListProfiles(ctx context.Context, req *proto.ListProfilesRequest) (*proto.ListProfilesResponse, error) { - profiles := []string{} + profiles, err := getProfiles(req.UserProfilesPath) + if err != nil { + return nil, err + } - ctx, cancel := context.WithCancel(ctx) - defer cancel() + profileNames := make([]string, len(profiles)) - if err := EnsureDefaultProfile(s, ctx); err != nil { - return nil, err + for i, profile := range profiles { + profileNames[i] = profile.Name } - configPath := s.latestConfigInput.ConfigPath - profilesPath := path.Join(path.Dir(configPath), "profiles") + return &proto.ListProfilesResponse{Profiles: profileNames}, nil +} + +func findProfile(req *proto.SwitchProfileRequest) (*Profile, error) { + var newProfile *Profile = nil - entries, err := os.ReadDir(profilesPath) + profiles, err := getProfiles(req.UserProfilesPath) if err != nil { - return nil, fmt.Errorf("failed to read profiles directory: %v", err) + return nil, err } - for _, entry := range entries { - if entry.IsDir() { - continue + for _, profile := range profiles { + if profile.Name == req.Profile { + newProfile = &profile } + } + + if newProfile != nil { + return newProfile, nil + } + + if req.IsNewSystemProfile == nil { + return nil, fmt.Errorf("profile %v does not exist", req.Profile) + } + + name := req.Profile + profilePath := path.Join(req.UserProfilesPath, req.Profile+".json") - name := strings.TrimSuffix(entry.Name(), ".json") - profiles = append(profiles, name) + if *req.IsNewSystemProfile { + profilePath = path.Join(systemProfilesPath, req.Profile+".json") } - return &proto.ListProfilesResponse{Profiles: profiles}, nil + log.Debugf("Creating new profile %v at %v", name, profilePath) + return &Profile{Name: name, Path: profilePath}, nil } // SwitchProfile switches to a different profile (network) @@ -128,35 +190,32 @@ func (s *Server) SwitchProfile(ctx context.Context, req *proto.SwitchProfileRequ ctx, cancel := context.WithCancel(ctx) defer cancel() - if err := EnsureDefaultProfile(s, ctx); err != nil { + if err := reconnectAfter(s, ctx); err != nil { return nil, err } - configPath := s.latestConfigInput.ConfigPath - profilePath := path.Join(path.Join(path.Dir(configPath), "profiles"), req.Profile+".json") - - log.Debugf("replacing config at %v with config at %v", configPath, profilePath) - - if _, err := os.Stat(profilePath); os.IsNotExist(err) { - return nil, fmt.Errorf("profile %v (%v) does not exist", req.Profile, profilePath) - } - - if err := os.Remove(configPath); err != nil { - return nil, fmt.Errorf("failed to remove old profile: %v", err) + newProfile, err := findProfile(req) + if err != nil { + return nil, err } - if err := os.Symlink(profilePath, configPath); err != nil { - return nil, fmt.Errorf("failed to copy new profile %v: %v", req.Profile, err) - } + log.Debugf("Reading new config %v from profile %v", newProfile.Path, newProfile.Name) - config, err := internal.UpdateOrCreateConfig(internal.ConfigInput{ConfigPath: configPath}) + configInput := internal.ConfigInput{ConfigPath: newProfile.Path} + config, err := internal.UpdateOrCreateConfig(configInput) if err != nil { return nil, err } s.mutex.Lock() + s.profileState.currentProfile = *newProfile + s.latestConfigInput = configInput s.config = config s.mutex.Unlock() + if err := s.profileState.WritePersistantProfile(newProfile); err != nil { + return nil, err + } + return &proto.SwitchProfileResponse{}, nil } diff --git a/client/server/server.go b/client/server/server.go index 8173d07413f..31ef19da802 100644 --- a/client/server/server.go +++ b/client/server/server.go @@ -61,6 +61,7 @@ type Server struct { statusRecorder *peer.Status sessionWatcher *internal.SessionWatcher + profileState *ProfileState mgmProbe *internal.Probe signalProbe *internal.Probe @@ -83,6 +84,8 @@ func New(ctx context.Context, configPath, logFile string) *Server { latestConfigInput: internal.ConfigInput{ ConfigPath: configPath, }, + profileState: &ProfileState{}, + logFile: logFile, mgmProbe: internal.NewProbe(), signalProbe: internal.NewProbe(), @@ -111,6 +114,12 @@ func (s *Server) Start() error { ctx, cancel := context.WithCancel(s.rootCtx) s.actCancel = cancel + if err := s.profileState.Init(); err != nil { + return err + } + + s.latestConfigInput = internal.ConfigInput{ConfigPath: s.profileState.currentProfile.Path} + // if configuration exists, we just start connections. if is new config we skip and set status NeedsLogin // on failure we return error to retry config, err := internal.UpdateConfig(s.latestConfigInput)