From 4fc062b3068975f6e83451c1805f2d060e759b90 Mon Sep 17 00:00:00 2001 From: Santtu Lakkala Date: Tue, 24 Sep 2024 13:00:58 +0300 Subject: [PATCH 1/6] Implement locale and timezone setting Add SetLocale and SetTimezone methods to givc-admin for changing the system settings. When set, the value are stored to a configuration file, and reported back to agents when they register. Adjust givc-agent to handle the timezone and locale settings repotrted by the admin; use systemd's localectl, timedatectl and systemctl set-environment to apply the settings. Add policykit policy to allow locale and timezone setting to the givc user. Signed-off-by: Santtu Lakkala --- Cargo.toml | 3 +- api/admin/admin.pb.go | 383 ++++++++++++++++++++++---------- api/admin/admin.proto | 13 +- api/admin/admin_grpc.pb.go | 76 ++++++- api/hwid/hwid.pb.go | 10 +- api/hwid/hwid_grpc.pb.go | 2 +- api/systemd/systemd.pb.go | 20 +- api/systemd/systemd_grpc.pb.go | 2 +- api/wifi/wifi.pb.go | 18 +- api/wifi/wifi_grpc.pb.go | 2 +- client/src/client.rs | 29 ++- internal/cmd/givc-agent/main.go | 23 +- nixos/modules/appvm.nix | 15 ++ src/admin/server.rs | 51 ++++- src/bin/givc-agent.rs | 20 +- src/bin/givc-cli.rs | 14 ++ 16 files changed, 524 insertions(+), 157 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a52bac8..c525f99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,8 @@ console = "0.15" http = "0.2" http-body = "0.4.2" hyper = "0.14" -tokio = {version = "1.0", features = ["rt-multi-thread", "time", "macros"]} +prost = "0.12" +tokio = {version = "1.0", features = ["rt-multi-thread", "time", "macros", "fs"]} tokio-stream = "0.1" tokio-vsock = "0.5" tonic = {version="0.12.2", features = ["tls"]} diff --git a/api/admin/admin.pb.go b/api/admin/admin.pb.go index 2a969fe..ba46fda 100644 --- a/api/admin/admin.pb.go +++ b/api/admin/admin.pb.go @@ -3,8 +3,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 -// protoc v4.25.4 +// protoc-gen-go v1.34.1 +// protoc v4.24.4 // source: admin.proto package admin @@ -257,7 +257,8 @@ type RegistryResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - CmdStatus string `protobuf:"bytes,1,opt,name=CmdStatus,proto3" json:"CmdStatus,omitempty"` + Timezone string `protobuf:"bytes,1,opt,name=Timezone,proto3" json:"Timezone,omitempty"` + Locale string `protobuf:"bytes,2,opt,name=Locale,proto3" json:"Locale,omitempty"` } func (x *RegistryResponse) Reset() { @@ -292,9 +293,16 @@ func (*RegistryResponse) Descriptor() ([]byte, []int) { return file_admin_proto_rawDescGZIP(), []int{3} } -func (x *RegistryResponse) GetCmdStatus() string { +func (x *RegistryResponse) GetTimezone() string { if x != nil { - return x.CmdStatus + return x.Timezone + } + return "" +} + +func (x *RegistryResponse) GetLocale() string { + if x != nil { + return x.Locale } return "" } @@ -682,6 +690,100 @@ func (*WatchItem_Updated) isWatchItem_Status() {} func (*WatchItem_Removed) isWatchItem_Status() {} +type LocaleRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Locale string `protobuf:"bytes,1,opt,name=Locale,proto3" json:"Locale,omitempty"` +} + +func (x *LocaleRequest) Reset() { + *x = LocaleRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_admin_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LocaleRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LocaleRequest) ProtoMessage() {} + +func (x *LocaleRequest) ProtoReflect() protoreflect.Message { + mi := &file_admin_proto_msgTypes[10] + 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 LocaleRequest.ProtoReflect.Descriptor instead. +func (*LocaleRequest) Descriptor() ([]byte, []int) { + return file_admin_proto_rawDescGZIP(), []int{10} +} + +func (x *LocaleRequest) GetLocale() string { + if x != nil { + return x.Locale + } + return "" +} + +type TimezoneRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Timezone string `protobuf:"bytes,1,opt,name=Timezone,proto3" json:"Timezone,omitempty"` +} + +func (x *TimezoneRequest) Reset() { + *x = TimezoneRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_admin_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TimezoneRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TimezoneRequest) ProtoMessage() {} + +func (x *TimezoneRequest) ProtoReflect() protoreflect.Message { + mi := &file_admin_proto_msgTypes[11] + 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 TimezoneRequest.ProtoReflect.Descriptor instead. +func (*TimezoneRequest) Descriptor() ([]byte, []int) { + return file_admin_proto_rawDescGZIP(), []int{11} +} + +func (x *TimezoneRequest) GetTimezone() string { + if x != nil { + return x.Timezone + } + return "" +} + var File_admin_proto protoreflect.FileDescriptor var file_admin_proto_rawDesc = []byte{ @@ -714,91 +816,104 @@ var file_admin_proto_rawDesc = []byte{ 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x27, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x55, 0x6e, 0x69, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x05, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x22, 0x30, 0x0a, 0x10, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x43, 0x6d, 0x64, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x43, 0x6d, 0x64, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x22, 0x6a, 0x0a, 0x12, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x41, 0x70, 0x70, - 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x41, 0x70, 0x70, 0x4e, - 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x56, 0x6d, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x56, 0x6d, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, - 0x12, 0x12, 0x0a, 0x04, 0x41, 0x72, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, - 0x41, 0x72, 0x67, 0x73, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x56, 0x6d, 0x4e, 0x61, 0x6d, 0x65, 0x22, - 0x51, 0x0a, 0x13, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x43, 0x6d, 0x64, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x43, 0x6d, 0x64, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x41, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x81, 0x01, 0x0a, 0x0d, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, - 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x56, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x56, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x1e, 0x0a, 0x0a, 0x54, 0x72, 0x75, 0x73, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x54, 0x72, 0x75, 0x73, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x22, - 0x3d, 0x0a, 0x11, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x22, 0xdd, - 0x01, 0x0a, 0x09, 0x57, 0x61, 0x74, 0x63, 0x68, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x34, 0x0a, 0x07, - 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, - 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x07, 0x49, 0x6e, 0x69, 0x74, 0x69, - 0x61, 0x6c, 0x12, 0x2c, 0x0a, 0x05, 0x41, 0x64, 0x64, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, - 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x48, 0x00, 0x52, 0x05, 0x41, 0x64, 0x64, 0x65, 0x64, - 0x12, 0x30, 0x0a, 0x07, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, - 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x48, 0x00, 0x52, 0x07, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x64, 0x12, 0x30, 0x0a, 0x07, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x51, 0x75, 0x65, 0x72, - 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x48, 0x00, 0x52, 0x07, 0x52, 0x65, 0x6d, - 0x6f, 0x76, 0x65, 0x64, 0x42, 0x08, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x32, 0x8f, - 0x05, 0x0a, 0x0c, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, - 0x44, 0x0a, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x12, 0x16, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x64, 0x6d, - 0x69, 0x6e, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x10, 0x53, 0x74, 0x61, 0x72, 0x74, 0x41, 0x70, - 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x61, 0x64, 0x6d, 0x69, - 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x41, 0x70, 0x70, - 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x10, 0x50, 0x61, 0x75, 0x73, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x41, - 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x4c, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x65, 0x22, 0x46, 0x0a, 0x10, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x22, 0x6a, 0x0a, 0x12, 0x41, 0x70, 0x70, + 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x18, 0x0a, 0x07, 0x41, 0x70, 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x41, 0x70, 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x56, 0x6d, 0x4e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x56, 0x6d, 0x4e, + 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x12, 0x0a, 0x04, 0x41, 0x72, 0x67, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x41, 0x72, 0x67, 0x73, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x56, + 0x6d, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x51, 0x0a, 0x13, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, + 0x43, 0x6d, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x43, 0x6d, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x41, 0x70, + 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x41, + 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x22, 0x81, 0x01, 0x0a, 0x0d, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, + 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x44, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x56, 0x6d, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x56, 0x6d, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x54, 0x72, 0x75, 0x73, 0x74, 0x4c, 0x65, + 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x54, 0x72, 0x75, 0x73, 0x74, + 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x3d, 0x0a, 0x11, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x4c, 0x69, + 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, + 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, + 0x4c, 0x69, 0x73, 0x74, 0x22, 0xdd, 0x01, 0x0a, 0x09, 0x57, 0x61, 0x74, 0x63, 0x68, 0x49, 0x74, + 0x65, 0x6d, 0x12, 0x34, 0x0a, 0x07, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, + 0x07, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x2c, 0x0a, 0x05, 0x41, 0x64, 0x64, 0x65, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x48, 0x00, 0x52, + 0x05, 0x41, 0x64, 0x64, 0x65, 0x64, 0x12, 0x30, 0x0a, 0x07, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x48, 0x00, 0x52, + 0x07, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x12, 0x30, 0x0a, 0x07, 0x52, 0x65, 0x6d, 0x6f, + 0x76, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x64, 0x6d, 0x69, + 0x6e, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x48, + 0x00, 0x52, 0x07, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x42, 0x08, 0x0a, 0x06, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x22, 0x27, 0x0a, 0x0d, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x22, 0x2d, 0x0a, + 0x0f, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1a, 0x0a, 0x08, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x32, 0xf9, 0x05, 0x0a, + 0x0c, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x44, 0x0a, + 0x0f, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x16, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, + 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x10, 0x53, 0x74, 0x61, 0x72, 0x74, 0x41, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, + 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x12, 0x4b, 0x0a, 0x10, 0x50, 0x61, 0x75, 0x73, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4a, 0x0a, - 0x0f, 0x53, 0x74, 0x6f, 0x70, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x19, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x64, - 0x6d, 0x69, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x28, 0x0a, 0x08, 0x50, 0x6f, 0x77, - 0x65, 0x72, 0x6f, 0x66, 0x66, 0x12, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x22, 0x00, 0x12, 0x26, 0x0a, 0x06, 0x52, 0x65, 0x62, 0x6f, 0x6f, 0x74, 0x12, 0x0c, 0x2e, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, + 0x11, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, + 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0f, 0x53, + 0x74, 0x6f, 0x70, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, + 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x64, 0x6d, 0x69, + 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x09, 0x53, 0x65, 0x74, 0x4c, 0x6f, + 0x63, 0x61, 0x6c, 0x65, 0x12, 0x14, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x4c, 0x6f, 0x63, + 0x61, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x61, 0x64, 0x6d, + 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x0b, 0x53, 0x65, + 0x74, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x12, 0x16, 0x2e, 0x61, 0x64, 0x6d, 0x69, + 0x6e, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, + 0x00, 0x12, 0x28, 0x0a, 0x08, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x6f, 0x66, 0x66, 0x12, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x61, 0x64, - 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x27, 0x0a, 0x07, 0x53, - 0x75, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x12, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x22, 0x00, 0x12, 0x26, 0x0a, 0x06, 0x57, 0x61, 0x6b, 0x65, 0x75, 0x70, 0x12, 0x0c, + 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x26, 0x0a, 0x06, 0x52, + 0x65, 0x62, 0x6f, 0x6f, 0x74, 0x12, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x22, 0x00, 0x12, 0x27, 0x0a, 0x07, 0x53, 0x75, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x12, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x61, - 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x09, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, - 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x18, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x2b, 0x0a, 0x05, 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, 0x0c, 0x2e, 0x61, - 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x10, 0x2e, 0x61, 0x64, 0x6d, - 0x69, 0x6e, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x49, 0x74, 0x65, 0x6d, 0x22, 0x00, 0x30, 0x01, - 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x26, 0x0a, 0x06, + 0x57, 0x61, 0x6b, 0x65, 0x75, 0x70, 0x12, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x09, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, + 0x74, 0x12, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, + 0x18, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2b, 0x0a, 0x05, 0x57, + 0x61, 0x74, 0x63, 0x68, 0x12, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x1a, 0x10, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, + 0x49, 0x74, 0x65, 0x6d, 0x22, 0x00, 0x30, 0x01, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x61, 0x64, + 0x6d, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -813,8 +928,8 @@ func file_admin_proto_rawDescGZIP() []byte { return file_admin_proto_rawDescData } -var file_admin_proto_msgTypes = make([]protoimpl.MessageInfo, 10) -var file_admin_proto_goTypes = []any{ +var file_admin_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_admin_proto_goTypes = []interface{}{ (*UnitStatus)(nil), // 0: admin.UnitStatus (*TransportConfig)(nil), // 1: admin.TransportConfig (*RegistryRequest)(nil), // 2: admin.RegistryRequest @@ -825,6 +940,8 @@ var file_admin_proto_goTypes = []any{ (*QueryListItem)(nil), // 7: admin.QueryListItem (*QueryListResponse)(nil), // 8: admin.QueryListResponse (*WatchItem)(nil), // 9: admin.WatchItem + (*LocaleRequest)(nil), // 10: admin.LocaleRequest + (*TimezoneRequest)(nil), // 11: admin.TimezoneRequest } var file_admin_proto_depIdxs = []int32{ 1, // 0: admin.RegistryRequest.Transport:type_name -> admin.TransportConfig @@ -839,25 +956,29 @@ var file_admin_proto_depIdxs = []int32{ 4, // 9: admin.AdminService.PauseApplication:input_type -> admin.ApplicationRequest 4, // 10: admin.AdminService.ResumeApplication:input_type -> admin.ApplicationRequest 4, // 11: admin.AdminService.StopApplication:input_type -> admin.ApplicationRequest - 6, // 12: admin.AdminService.Poweroff:input_type -> admin.Empty - 6, // 13: admin.AdminService.Reboot:input_type -> admin.Empty - 6, // 14: admin.AdminService.Suspend:input_type -> admin.Empty - 6, // 15: admin.AdminService.Wakeup:input_type -> admin.Empty - 6, // 16: admin.AdminService.QueryList:input_type -> admin.Empty - 6, // 17: admin.AdminService.Watch:input_type -> admin.Empty - 3, // 18: admin.AdminService.RegisterService:output_type -> admin.RegistryResponse - 5, // 19: admin.AdminService.StartApplication:output_type -> admin.ApplicationResponse - 5, // 20: admin.AdminService.PauseApplication:output_type -> admin.ApplicationResponse - 5, // 21: admin.AdminService.ResumeApplication:output_type -> admin.ApplicationResponse - 5, // 22: admin.AdminService.StopApplication:output_type -> admin.ApplicationResponse - 6, // 23: admin.AdminService.Poweroff:output_type -> admin.Empty - 6, // 24: admin.AdminService.Reboot:output_type -> admin.Empty - 6, // 25: admin.AdminService.Suspend:output_type -> admin.Empty - 6, // 26: admin.AdminService.Wakeup:output_type -> admin.Empty - 8, // 27: admin.AdminService.QueryList:output_type -> admin.QueryListResponse - 9, // 28: admin.AdminService.Watch:output_type -> admin.WatchItem - 18, // [18:29] is the sub-list for method output_type - 7, // [7:18] is the sub-list for method input_type + 10, // 12: admin.AdminService.SetLocale:input_type -> admin.LocaleRequest + 11, // 13: admin.AdminService.SetTimezone:input_type -> admin.TimezoneRequest + 6, // 14: admin.AdminService.Poweroff:input_type -> admin.Empty + 6, // 15: admin.AdminService.Reboot:input_type -> admin.Empty + 6, // 16: admin.AdminService.Suspend:input_type -> admin.Empty + 6, // 17: admin.AdminService.Wakeup:input_type -> admin.Empty + 6, // 18: admin.AdminService.QueryList:input_type -> admin.Empty + 6, // 19: admin.AdminService.Watch:input_type -> admin.Empty + 3, // 20: admin.AdminService.RegisterService:output_type -> admin.RegistryResponse + 5, // 21: admin.AdminService.StartApplication:output_type -> admin.ApplicationResponse + 5, // 22: admin.AdminService.PauseApplication:output_type -> admin.ApplicationResponse + 5, // 23: admin.AdminService.ResumeApplication:output_type -> admin.ApplicationResponse + 5, // 24: admin.AdminService.StopApplication:output_type -> admin.ApplicationResponse + 6, // 25: admin.AdminService.SetLocale:output_type -> admin.Empty + 6, // 26: admin.AdminService.SetTimezone:output_type -> admin.Empty + 6, // 27: admin.AdminService.Poweroff:output_type -> admin.Empty + 6, // 28: admin.AdminService.Reboot:output_type -> admin.Empty + 6, // 29: admin.AdminService.Suspend:output_type -> admin.Empty + 6, // 30: admin.AdminService.Wakeup:output_type -> admin.Empty + 8, // 31: admin.AdminService.QueryList:output_type -> admin.QueryListResponse + 9, // 32: admin.AdminService.Watch:output_type -> admin.WatchItem + 20, // [20:33] is the sub-list for method output_type + 7, // [7:20] is the sub-list for method input_type 7, // [7:7] is the sub-list for extension type_name 7, // [7:7] is the sub-list for extension extendee 0, // [0:7] is the sub-list for field type_name @@ -869,7 +990,7 @@ func file_admin_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_admin_proto_msgTypes[0].Exporter = func(v any, i int) any { + file_admin_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UnitStatus); i { case 0: return &v.state @@ -881,7 +1002,7 @@ func file_admin_proto_init() { return nil } } - file_admin_proto_msgTypes[1].Exporter = func(v any, i int) any { + file_admin_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TransportConfig); i { case 0: return &v.state @@ -893,7 +1014,7 @@ func file_admin_proto_init() { return nil } } - file_admin_proto_msgTypes[2].Exporter = func(v any, i int) any { + file_admin_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RegistryRequest); i { case 0: return &v.state @@ -905,7 +1026,7 @@ func file_admin_proto_init() { return nil } } - file_admin_proto_msgTypes[3].Exporter = func(v any, i int) any { + file_admin_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RegistryResponse); i { case 0: return &v.state @@ -917,7 +1038,7 @@ func file_admin_proto_init() { return nil } } - file_admin_proto_msgTypes[4].Exporter = func(v any, i int) any { + file_admin_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ApplicationRequest); i { case 0: return &v.state @@ -929,7 +1050,7 @@ func file_admin_proto_init() { return nil } } - file_admin_proto_msgTypes[5].Exporter = func(v any, i int) any { + file_admin_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ApplicationResponse); i { case 0: return &v.state @@ -941,7 +1062,7 @@ func file_admin_proto_init() { return nil } } - file_admin_proto_msgTypes[6].Exporter = func(v any, i int) any { + file_admin_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Empty); i { case 0: return &v.state @@ -953,7 +1074,7 @@ func file_admin_proto_init() { return nil } } - file_admin_proto_msgTypes[7].Exporter = func(v any, i int) any { + file_admin_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*QueryListItem); i { case 0: return &v.state @@ -965,7 +1086,7 @@ func file_admin_proto_init() { return nil } } - file_admin_proto_msgTypes[8].Exporter = func(v any, i int) any { + file_admin_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*QueryListResponse); i { case 0: return &v.state @@ -977,7 +1098,7 @@ func file_admin_proto_init() { return nil } } - file_admin_proto_msgTypes[9].Exporter = func(v any, i int) any { + file_admin_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*WatchItem); i { case 0: return &v.state @@ -989,9 +1110,33 @@ func file_admin_proto_init() { return nil } } + file_admin_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LocaleRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_admin_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TimezoneRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } - file_admin_proto_msgTypes[4].OneofWrappers = []any{} - file_admin_proto_msgTypes[9].OneofWrappers = []any{ + file_admin_proto_msgTypes[4].OneofWrappers = []interface{}{} + file_admin_proto_msgTypes[9].OneofWrappers = []interface{}{ (*WatchItem_Initial)(nil), (*WatchItem_Added)(nil), (*WatchItem_Updated)(nil), @@ -1003,7 +1148,7 @@ func file_admin_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_admin_proto_rawDesc, NumEnums: 0, - NumMessages: 10, + NumMessages: 12, NumExtensions: 0, NumServices: 1, }, diff --git a/api/admin/admin.proto b/api/admin/admin.proto index b371dc5..035ad6a 100644 --- a/api/admin/admin.proto +++ b/api/admin/admin.proto @@ -28,7 +28,8 @@ message RegistryRequest { } message RegistryResponse { - string CmdStatus = 1; + string Timezone = 1; + string Locale = 2; } message ApplicationRequest { @@ -65,12 +66,22 @@ message WatchItem { } } +message LocaleRequest { + string Locale = 1; +} + +message TimezoneRequest { + string Timezone = 1; +} + service AdminService { rpc RegisterService(RegistryRequest) returns (RegistryResponse) {} rpc StartApplication(ApplicationRequest) returns (ApplicationResponse) {} rpc PauseApplication(ApplicationRequest) returns (ApplicationResponse) {} rpc ResumeApplication(ApplicationRequest) returns (ApplicationResponse) {} rpc StopApplication(ApplicationRequest) returns (ApplicationResponse) {} + rpc SetLocale(LocaleRequest) returns (Empty) {} + rpc SetTimezone(TimezoneRequest) returns (Empty) {} rpc Poweroff(Empty) returns (Empty) {} rpc Reboot(Empty) returns (Empty) {} rpc Suspend(Empty) returns (Empty) {} diff --git a/api/admin/admin_grpc.pb.go b/api/admin/admin_grpc.pb.go index dc85519..a37c0ff 100644 --- a/api/admin/admin_grpc.pb.go +++ b/api/admin/admin_grpc.pb.go @@ -4,7 +4,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.3.0 -// - protoc v4.25.4 +// - protoc v4.24.4 // source: admin.proto package admin @@ -27,6 +27,8 @@ const ( AdminService_PauseApplication_FullMethodName = "/admin.AdminService/PauseApplication" AdminService_ResumeApplication_FullMethodName = "/admin.AdminService/ResumeApplication" AdminService_StopApplication_FullMethodName = "/admin.AdminService/StopApplication" + AdminService_SetLocale_FullMethodName = "/admin.AdminService/SetLocale" + AdminService_SetTimezone_FullMethodName = "/admin.AdminService/SetTimezone" AdminService_Poweroff_FullMethodName = "/admin.AdminService/Poweroff" AdminService_Reboot_FullMethodName = "/admin.AdminService/Reboot" AdminService_Suspend_FullMethodName = "/admin.AdminService/Suspend" @@ -44,6 +46,8 @@ type AdminServiceClient interface { PauseApplication(ctx context.Context, in *ApplicationRequest, opts ...grpc.CallOption) (*ApplicationResponse, error) ResumeApplication(ctx context.Context, in *ApplicationRequest, opts ...grpc.CallOption) (*ApplicationResponse, error) StopApplication(ctx context.Context, in *ApplicationRequest, opts ...grpc.CallOption) (*ApplicationResponse, error) + SetLocale(ctx context.Context, in *LocaleRequest, opts ...grpc.CallOption) (*Empty, error) + SetTimezone(ctx context.Context, in *TimezoneRequest, opts ...grpc.CallOption) (*Empty, error) Poweroff(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) Reboot(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) Suspend(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) @@ -105,6 +109,24 @@ func (c *adminServiceClient) StopApplication(ctx context.Context, in *Applicatio return out, nil } +func (c *adminServiceClient) SetLocale(ctx context.Context, in *LocaleRequest, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, AdminService_SetLocale_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *adminServiceClient) SetTimezone(ctx context.Context, in *TimezoneRequest, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, AdminService_SetTimezone_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *adminServiceClient) Poweroff(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) { out := new(Empty) err := c.cc.Invoke(ctx, AdminService_Poweroff_FullMethodName, in, out, opts...) @@ -191,6 +213,8 @@ type AdminServiceServer interface { PauseApplication(context.Context, *ApplicationRequest) (*ApplicationResponse, error) ResumeApplication(context.Context, *ApplicationRequest) (*ApplicationResponse, error) StopApplication(context.Context, *ApplicationRequest) (*ApplicationResponse, error) + SetLocale(context.Context, *LocaleRequest) (*Empty, error) + SetTimezone(context.Context, *TimezoneRequest) (*Empty, error) Poweroff(context.Context, *Empty) (*Empty, error) Reboot(context.Context, *Empty) (*Empty, error) Suspend(context.Context, *Empty) (*Empty, error) @@ -219,6 +243,12 @@ func (UnimplementedAdminServiceServer) ResumeApplication(context.Context, *Appli func (UnimplementedAdminServiceServer) StopApplication(context.Context, *ApplicationRequest) (*ApplicationResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method StopApplication not implemented") } +func (UnimplementedAdminServiceServer) SetLocale(context.Context, *LocaleRequest) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetLocale not implemented") +} +func (UnimplementedAdminServiceServer) SetTimezone(context.Context, *TimezoneRequest) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetTimezone not implemented") +} func (UnimplementedAdminServiceServer) Poweroff(context.Context, *Empty) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method Poweroff not implemented") } @@ -340,6 +370,42 @@ func _AdminService_StopApplication_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } +func _AdminService_SetLocale_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(LocaleRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdminServiceServer).SetLocale(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdminService_SetLocale_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdminServiceServer).SetLocale(ctx, req.(*LocaleRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdminService_SetTimezone_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TimezoneRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdminServiceServer).SetTimezone(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdminService_SetTimezone_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdminServiceServer).SetTimezone(ctx, req.(*TimezoneRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _AdminService_Poweroff_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(Empty) if err := dec(in); err != nil { @@ -478,6 +544,14 @@ var AdminService_ServiceDesc = grpc.ServiceDesc{ MethodName: "StopApplication", Handler: _AdminService_StopApplication_Handler, }, + { + MethodName: "SetLocale", + Handler: _AdminService_SetLocale_Handler, + }, + { + MethodName: "SetTimezone", + Handler: _AdminService_SetTimezone_Handler, + }, { MethodName: "Poweroff", Handler: _AdminService_Poweroff_Handler, diff --git a/api/hwid/hwid.pb.go b/api/hwid/hwid.pb.go index db38367..ac069c8 100644 --- a/api/hwid/hwid.pb.go +++ b/api/hwid/hwid.pb.go @@ -3,8 +3,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 -// protoc v4.25.4 +// protoc-gen-go v1.34.1 +// protoc v4.24.4 // source: hwid.proto package hwid @@ -137,7 +137,7 @@ func file_hwid_proto_rawDescGZIP() []byte { } var file_hwid_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_hwid_proto_goTypes = []any{ +var file_hwid_proto_goTypes = []interface{}{ (*HwIdRequest)(nil), // 0: hwid.HwIdRequest (*HwIdResponse)(nil), // 1: hwid.HwIdResponse } @@ -157,7 +157,7 @@ func file_hwid_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_hwid_proto_msgTypes[0].Exporter = func(v any, i int) any { + file_hwid_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*HwIdRequest); i { case 0: return &v.state @@ -169,7 +169,7 @@ func file_hwid_proto_init() { return nil } } - file_hwid_proto_msgTypes[1].Exporter = func(v any, i int) any { + file_hwid_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*HwIdResponse); i { case 0: return &v.state diff --git a/api/hwid/hwid_grpc.pb.go b/api/hwid/hwid_grpc.pb.go index 68b84e4..d3099a5 100644 --- a/api/hwid/hwid_grpc.pb.go +++ b/api/hwid/hwid_grpc.pb.go @@ -4,7 +4,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.3.0 -// - protoc v4.25.4 +// - protoc v4.24.4 // source: hwid.proto package hwid diff --git a/api/systemd/systemd.pb.go b/api/systemd/systemd.pb.go index f35c87b..e606890 100644 --- a/api/systemd/systemd.pb.go +++ b/api/systemd/systemd.pb.go @@ -3,8 +3,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 -// protoc v4.25.4 +// protoc-gen-go v1.34.1 +// protoc v4.24.4 // source: systemd.proto package systemd @@ -507,7 +507,7 @@ func file_systemd_proto_rawDescGZIP() []byte { } var file_systemd_proto_msgTypes = make([]protoimpl.MessageInfo, 7) -var file_systemd_proto_goTypes = []any{ +var file_systemd_proto_goTypes = []interface{}{ (*UnitRequest)(nil), // 0: systemd.UnitRequest (*UnitResponse)(nil), // 1: systemd.UnitResponse (*AppUnitRequest)(nil), // 2: systemd.AppUnitRequest @@ -547,7 +547,7 @@ func file_systemd_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_systemd_proto_msgTypes[0].Exporter = func(v any, i int) any { + file_systemd_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UnitRequest); i { case 0: return &v.state @@ -559,7 +559,7 @@ func file_systemd_proto_init() { return nil } } - file_systemd_proto_msgTypes[1].Exporter = func(v any, i int) any { + file_systemd_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UnitResponse); i { case 0: return &v.state @@ -571,7 +571,7 @@ func file_systemd_proto_init() { return nil } } - file_systemd_proto_msgTypes[2].Exporter = func(v any, i int) any { + file_systemd_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AppUnitRequest); i { case 0: return &v.state @@ -583,7 +583,7 @@ func file_systemd_proto_init() { return nil } } - file_systemd_proto_msgTypes[3].Exporter = func(v any, i int) any { + file_systemd_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UnitStatus); i { case 0: return &v.state @@ -595,7 +595,7 @@ func file_systemd_proto_init() { return nil } } - file_systemd_proto_msgTypes[4].Exporter = func(v any, i int) any { + file_systemd_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UnitStatusResponse); i { case 0: return &v.state @@ -607,7 +607,7 @@ func file_systemd_proto_init() { return nil } } - file_systemd_proto_msgTypes[5].Exporter = func(v any, i int) any { + file_systemd_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UnitResourceRequest); i { case 0: return &v.state @@ -619,7 +619,7 @@ func file_systemd_proto_init() { return nil } } - file_systemd_proto_msgTypes[6].Exporter = func(v any, i int) any { + file_systemd_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UnitResourceResponse); i { case 0: return &v.state diff --git a/api/systemd/systemd_grpc.pb.go b/api/systemd/systemd_grpc.pb.go index 60fea3f..ef30f63 100644 --- a/api/systemd/systemd_grpc.pb.go +++ b/api/systemd/systemd_grpc.pb.go @@ -4,7 +4,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.3.0 -// - protoc v4.25.4 +// - protoc v4.24.4 // source: systemd.proto package systemd diff --git a/api/wifi/wifi.pb.go b/api/wifi/wifi.pb.go index 84dd9bd..849bd34 100644 --- a/api/wifi/wifi.pb.go +++ b/api/wifi/wifi.pb.go @@ -3,8 +3,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 -// protoc v4.25.4 +// protoc-gen-go v1.34.1 +// protoc v4.24.4 // source: wifi.proto package wifi @@ -416,7 +416,7 @@ func file_wifi_proto_rawDescGZIP() []byte { } var file_wifi_proto_msgTypes = make([]protoimpl.MessageInfo, 6) -var file_wifi_proto_goTypes = []any{ +var file_wifi_proto_goTypes = []interface{}{ (*EmptyRequest)(nil), // 0: wifimanager.EmptyRequest (*WifiNetworkRequest)(nil), // 1: wifimanager.WifiNetworkRequest (*WifiConnectionRequest)(nil), // 2: wifimanager.WifiConnectionRequest @@ -451,7 +451,7 @@ func file_wifi_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_wifi_proto_msgTypes[0].Exporter = func(v any, i int) any { + file_wifi_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*EmptyRequest); i { case 0: return &v.state @@ -463,7 +463,7 @@ func file_wifi_proto_init() { return nil } } - file_wifi_proto_msgTypes[1].Exporter = func(v any, i int) any { + file_wifi_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*WifiNetworkRequest); i { case 0: return &v.state @@ -475,7 +475,7 @@ func file_wifi_proto_init() { return nil } } - file_wifi_proto_msgTypes[2].Exporter = func(v any, i int) any { + file_wifi_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*WifiConnectionRequest); i { case 0: return &v.state @@ -487,7 +487,7 @@ func file_wifi_proto_init() { return nil } } - file_wifi_proto_msgTypes[3].Exporter = func(v any, i int) any { + file_wifi_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AccessPoint); i { case 0: return &v.state @@ -499,7 +499,7 @@ func file_wifi_proto_init() { return nil } } - file_wifi_proto_msgTypes[4].Exporter = func(v any, i int) any { + file_wifi_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*WifiNetworkResponse); i { case 0: return &v.state @@ -511,7 +511,7 @@ func file_wifi_proto_init() { return nil } } - file_wifi_proto_msgTypes[5].Exporter = func(v any, i int) any { + file_wifi_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*WifiConnectionResponse); i { case 0: return &v.state diff --git a/api/wifi/wifi_grpc.pb.go b/api/wifi/wifi_grpc.pb.go index 6cbaf0a..6656cfb 100644 --- a/api/wifi/wifi_grpc.pb.go +++ b/api/wifi/wifi_grpc.pb.go @@ -4,7 +4,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.3.0 -// - protoc v4.25.4 +// - protoc v4.24.4 // source: wifi.proto package wifi diff --git a/client/src/client.rs b/client/src/client.rs index d031fb1..86643bc 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -71,17 +71,22 @@ impl AdminClient { ty: UnitType, endpoint: EndpointEntry, status: UnitStatus, - ) -> anyhow::Result { + ) -> anyhow::Result<(String, String)> { // Convert everything into wire format let request = pb::admin::RegistryRequest { - name: name, + name, parent: "".to_owned(), r#type: ty.into(), transport: Some(endpoint.into()), state: Some(status.into()), }; - let response = self.connect_to().await?.register_service(request).await?; - Ok(response.into_inner().cmd_status) + let response = self + .connect_to() + .await? + .register_service(request) + .await? + .into_inner(); + Ok((response.timezone, response.locale)) } pub async fn start( @@ -150,6 +155,22 @@ impl AdminClient { .collect() } + pub async fn set_locale(&self, locale: String) -> anyhow::Result<()> { + self.connect_to() + .await? + .set_locale(pb::admin::LocaleRequest { locale }) + .await?; + Ok(()) + } + + pub async fn set_timezone(&self, timezone: String) -> anyhow::Result<()> { + self.connect_to() + .await? + .set_timezone(pb::admin::TimezoneRequest { timezone }) + .await?; + Ok(()) + } + pub async fn watch(&self) -> anyhow::Result { let (tx, rx) = async_channel::bounded::(10); diff --git a/internal/cmd/givc-agent/main.go b/internal/cmd/givc-agent/main.go index 1477704..beffde0 100644 --- a/internal/cmd/givc-agent/main.go +++ b/internal/cmd/givc-agent/main.go @@ -6,6 +6,7 @@ import ( "context" "crypto/tls" "os" + "os/exec" "path/filepath" "strconv" "strings" @@ -176,9 +177,29 @@ func main() { <-serverStarted // Register agent - _, err := serviceclient.RegisterRemoteService(cfgAdminServer, agentEntryRequest) + response, err := serviceclient.RegisterRemoteService(cfgAdminServer, agentEntryRequest) if err != nil { log.Fatalf("Error register agent: %s", err) + } else { + if response.Locale != "" { + if err := exec.Command("localectl", "set-locale", response.Locale).Run(); err != nil { + log.Warningf("Failed to set locale: %s", err) + } + if givc_util.IsRoot() { + if err := exec.Command("systemctl", "set-environment", "LANG="+response.Locale).Run(); err != nil { + log.Warningf("Failed to set environment: %s", err) + } + } else { + if err := exec.Command("systemctl", "--user", "set-environment", "LANG="+response.Locale).Run(); err != nil { + log.Warningf("Failed to set environment: %s", err) + } + } + } + if response.Timezone != "" { + if err := exec.Command("timedatectl", "set-timezone", response.Timezone).Run(); err != nil { + log.Warningf("Failed to set timezone: %s", err) + } + } } // Register services diff --git a/nixos/modules/appvm.nix b/nixos/modules/appvm.nix index 062bb67..d6d851f 100644 --- a/nixos/modules/appvm.nix +++ b/nixos/modules/appvm.nix @@ -175,6 +175,21 @@ in } ]; + security.polkit = { + enable = true; + extraConfig = '' + polkit.addRule(function(action, subject) { + if (action.id == "org.freedesktop.locale1.set-locale" && subject.user == "ghaf") { + return polkit.Result.YES; + } + }); + polkit.addRule(function(action, subject) { + if (action.id == "org.freedesktop.timedate1.set-timezone" && subject.user == "ghaf") { + return polkit.Result.YES; + } + }); + ''; + }; systemd.user.services."givc-${cfg.name}" = { description = "GIVC remote service manager for application VMs."; enable = true; diff --git a/src/admin/server.rs b/src/admin/server.rs index 6ebae05..9ea4286 100644 --- a/src/admin/server.rs +++ b/src/admin/server.rs @@ -6,6 +6,7 @@ use givc_common::query::Event; use std::pin::Pin; use std::sync::Arc; use std::time::Duration; +use tokio::sync::Mutex; use tonic::{Code, Response, Status}; use tracing::{debug, error, info}; @@ -20,6 +21,8 @@ use givc_client::endpoint::{EndpointConfig, TlsConfig}; use givc_common::query::*; const VM_STARTUP_TIME: Duration = Duration::new(10, 0); +const TIMEZONE_CONF: &str = "/etc/timezone.conf"; +const LOCALE_CONF: &str = "/etc/locale-givc.conf"; // FIXME: this is almost copy of sysfsm::Event. #[derive(Copy, Clone, Debug, PartialEq)] @@ -35,6 +38,8 @@ pub struct AdminServiceImpl { registry: Registry, state: State, // FIXME: use sysfsm statemachine tls_config: Option, + locale: Mutex, + timezone: Mutex, } #[derive(Debug, Clone)] @@ -49,16 +54,31 @@ impl AdminService { tokio::task::spawn(async move { clone.monitor().await; }); - Self { inner: inner } + Self { inner } } } impl AdminServiceImpl { pub fn new(use_tls: Option) -> Self { + let timezone = std::fs::read_to_string(TIMEZONE_CONF) + .ok() + .and_then(|l| l.lines().next().map(ToOwned::to_owned)) + .unwrap_or_default(); + let locale = std::fs::read_to_string(LOCALE_CONF) + .ok() + .and_then(|l| { + l.lines() + .filter_map(|l| l.strip_prefix("LANG=")) + .next() + .map(ToOwned::to_owned) + }) + .unwrap_or_default(); Self { registry: Registry::new(), state: State::Init, tls_config: use_tls, + timezone: Mutex::new(timezone), + locale: Mutex::new(locale), } } @@ -300,13 +320,16 @@ impl pb::admin_service_server::AdminService for AdminService { ) -> std::result::Result, tonic::Status> { let req = request.into_inner(); + info!("Registering service {:?}", req); let entry = RegistryEntry::try_from(req) .map_err(|e| Status::new(Code::InvalidArgument, format!("{e}")))?; self.inner.register(entry); let res = RegistryResponse { - cmd_status: String::from("Registration successful"), + timezone: self.inner.timezone.lock().await.clone(), + locale: self.inner.locale.lock().await.clone(), }; + info!("Responding with {res:?}"); Ok(Response::new(res)) } async fn start_application( @@ -430,6 +453,30 @@ impl pb::admin_service_server::AdminService for AdminService { .await } + async fn set_locale( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + escalate(request, |req| async move { + let _ = tokio::fs::write(LOCALE_CONF, format!("LANG={}", req.locale)).await; + *self.inner.locale.lock().await = req.locale; + Ok(Empty {}) + }) + .await + } + + async fn set_timezone( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + escalate(request, |req| async move { + let _ = tokio::fs::write(TIMEZONE_CONF, &req.timezone).await; + *self.inner.timezone.lock().await = req.timezone; + Ok(Empty {}) + }) + .await + } + type WatchStream = Stream; async fn watch( &self, diff --git a/src/bin/givc-agent.rs b/src/bin/givc-agent.rs index 36a4cdc..e464209 100644 --- a/src/bin/givc-agent.rs +++ b/src/bin/givc-agent.rs @@ -9,6 +9,7 @@ use givc_common::pb; use givc_common::pb::reflection::SYSTEMD_DESCRIPTOR; use std::net::SocketAddr; use std::path::PathBuf; +use std::process::{Command, Stdio}; use tonic::transport::Server; use tracing::info; @@ -107,10 +108,27 @@ async fn main() -> std::result::Result<(), Box> { let admin_tls = tls.clone().map(|tls| (cli.admin_server_name, tls)); let admin = AdminClient::new(cli.admin_server_addr, cli.admin_server_port, admin_tls); - admin + let (timezone, locale) = admin .register_service(agent_service_name, cli.r#type.try_into()?, endpoint, status) .await?; + if !timezone.is_empty() { + Command::new("timedatectl") + .args(["set-timezone", timezone.as_str()]) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()?; + } + if !locale.is_empty() { + Command::new("localectl") + .args(["set-locale", locale.as_str()]) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()?; + } + let reflect = tonic_reflection::server::Builder::configure() .register_encoded_file_descriptor_set(SYSTEMD_DESCRIPTOR) .build() diff --git a/src/bin/givc-cli.rs b/src/bin/givc-cli.rs index c417c64..b7ff64d 100644 --- a/src/bin/givc-cli.rs +++ b/src/bin/givc-cli.rs @@ -79,6 +79,12 @@ enum Commands { #[arg(long, default_value_t = false)] as_json: bool, }, + SetLocale { + locale: String, + }, + SetTimezone { + timezone: String, + }, Watch { #[arg(long, default_value_t = false)] as_json: bool, @@ -173,6 +179,14 @@ async fn main() -> std::result::Result<(), Box> { let reply = admin.query_list().await?; dump(&reply, as_json)? } + + Commands::SetLocale { locale } => { + admin.set_locale(locale).await?; + } + + Commands::SetTimezone { timezone } => { + admin.set_timezone(timezone).await?; + } Commands::Watch { as_json, limit, From 3a051fb75c194f4ac6a6faf1094890574dc26a5c Mon Sep 17 00:00:00 2001 From: Santtu Lakkala Date: Tue, 1 Oct 2024 13:26:57 +0300 Subject: [PATCH 2/6] Fix clippy warnings, other cleanups Signed-off-by: Santtu Lakkala --- Cargo.lock | 1 + client/src/client.rs | 45 +++++++++++---------------- client/src/endpoint.rs | 12 ++++---- common/src/address.rs | 5 --- common/src/query.rs | 26 ++++++++-------- common/src/types.rs | 54 ++++++++++++++++----------------- flake.nix | 7 +++-- internal/cmd/givc-agent/main.go | 2 +- src/admin/entry.rs | 18 +++++------ src/admin/registry.rs | 18 ++++++++--- src/admin/server.rs | 14 ++++----- src/bin/givc-admin.rs | 2 +- src/bin/givc-agent.rs | 2 +- src/bin/givc-cli.rs | 44 +++++++++++++++------------ src/systemd_api/client.rs | 2 +- src/systemd_api/server.rs | 4 +-- src/utils/auth.rs | 2 +- src/utils/naming.rs | 2 +- src/utils/vsock.rs | 2 +- 19 files changed, 133 insertions(+), 129 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f6d1959..b9f1057 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -594,6 +594,7 @@ dependencies = [ "http 0.2.12", "http-body 0.4.6", "hyper 0.14.30", + "prost 0.12.6", "serde", "serde_json", "strum 0.25.0", diff --git a/client/src/client.rs b/client/src/client.rs index 86643bc..855818d 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -1,32 +1,27 @@ use anyhow::bail; use async_channel::Receiver; +use givc_common::pb; +pub use givc_common::query::{Event, QueryResult}; +use std::future::Future; +use std::pin::Pin; use tokio_stream::StreamExt; use tonic::transport::Channel; use tracing::debug; use givc_common::address::EndpointAddress; -use givc_common::pb; -pub use givc_common::query::{Event, QueryResult}; use givc_common::types::*; use crate::endpoint::{EndpointConfig, TlsConfig}; type Client = pb::admin_service_client::AdminServiceClient; -#[derive(Debug)] pub struct WatchResult { pub initial: Vec, // Design defence: we use `async-channel` here, as it could be used with both // tokio's and glib's eventloop, and recommended by gtk4-rs developers: pub channel: Receiver, - task: tokio::task::JoinHandle<()>, -} - -impl Drop for WatchResult { - fn drop(&mut self) { - self.task.abort() - } + pub task: Pin>>, } #[derive(Debug)] @@ -47,20 +42,17 @@ impl AdminClient { } pub fn from_endpoint_address( - addr: EndpointAddress, + address: EndpointAddress, tls_info: Option<(String, TlsConfig)>, ) -> Self { - let (name, tls) = match tls_info { + let (tls_name, tls) = match tls_info { Some((name, tls)) => (name, Some(tls)), None => (String::from("bogus(no tls)"), None), }; Self { endpoint: EndpointConfig { - transport: TransportConfig { - address: addr, - tls_name: name, - }, - tls: tls, + transport: TransportConfig { address, tls_name }, + tls, }, } } @@ -100,8 +92,7 @@ impl AdminClient { vm_name, args, }; - let response = self.connect_to().await?.start_application(request).await?; - // Ok(response.into_inner().cmd_status) + let _response = self.connect_to().await?.start_application(request).await?; Ok(()) } pub async fn stop(&self, _app: String) -> anyhow::Result<()> { @@ -172,6 +163,8 @@ impl AdminClient { } pub async fn watch(&self) -> anyhow::Result { + use pb::admin::watch_item::Status; + use pb::admin::WatchItem; let (tx, rx) = async_channel::bounded::(10); let mut watch = self @@ -182,15 +175,13 @@ impl AdminClient { .into_inner(); let list = match watch.try_next().await? { - Some(first) => match first.status { - Some(pb::admin::watch_item::Status::Initial(init)) => QueryResult::parse_list(init.list)?, - Some(item) => bail!("Protocol error, first item in stream not pb::admin::watch_item::Status::Initial, {:?}", item), - None => bail!("Protocol error, initial item missing"), - }, + Some(WatchItem { status: Some(Status::Initial(init)) }) => QueryResult::parse_list(init.list)?, + Some(WatchItem { status: Some(item) }) => bail!("Protocol error, first item in stream not pb::admin::watch_item::Status::Initial, {:?}", item), + Some(_) => bail!("Protocol error, initial item missing"), None => bail!("Protocol error, status field missing"), }; - let task = tokio::task::spawn(async move { + let task = async move { loop { if let Ok(Some(event)) = watch.try_next().await { let event = match Event::try_from(event) { @@ -209,12 +200,12 @@ impl AdminClient { break; } } - }); + }; let result = WatchResult { initial: list, channel: rx, - task, + task: Box::pin(task), }; Ok(result) } diff --git a/client/src/endpoint.rs b/client/src/endpoint.rs index 95c63ff..02a5fd6 100644 --- a/client/src/endpoint.rs +++ b/client/src/endpoint.rs @@ -1,4 +1,4 @@ -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::time::Duration; use anyhow::anyhow; @@ -32,11 +32,11 @@ pub struct EndpointConfig { impl TlsConfig { pub fn client_config(&self) -> anyhow::Result { - let pem = std::fs::read_to_string(self.ca_cert_file_path.as_os_str())?; + let pem = std::fs::read(&self.ca_cert_file_path)?; let ca = Certificate::from_pem(pem); - let client_cert = std::fs::read(self.cert_file_path.as_os_str())?; - let client_key = std::fs::read(self.key_file_path.as_os_str())?; + let client_cert = std::fs::read(&self.cert_file_path)?; + let client_key = std::fs::read(&self.key_file_path)?; let client_identity = Identity::from_pem(client_cert, client_key); let tls_name = self .tls_name @@ -49,8 +49,8 @@ impl TlsConfig { } pub fn server_config(&self) -> anyhow::Result { - let cert = std::fs::read(self.cert_file_path.as_os_str())?; - let key = std::fs::read(self.key_file_path.as_os_str())?; + let cert = std::fs::read(&self.cert_file_path)?; + let key = std::fs::read(&self.key_file_path)?; let identity = Identity::from_pem(cert, key); let config = ServerTlsConfig::new().identity(identity); Ok(config) diff --git a/common/src/address.rs b/common/src/address.rs index f7189c7..ae8884d 100644 --- a/common/src/address.rs +++ b/common/src/address.rs @@ -1,11 +1,6 @@ -use std::convert::{Into, TryFrom}; //use std::net::SocketAddr; -use std::path::PathBuf; - use tokio_vsock::VsockAddr; -use crate::pb; - #[derive(Clone, Debug, PartialEq)] pub enum EndpointAddress { Tcp { diff --git a/common/src/query.rs b/common/src/query.rs index 875d026..e96897f 100644 --- a/common/src/query.rs +++ b/common/src/query.rs @@ -55,13 +55,13 @@ impl TryFrom for QueryResult { } } -impl Into for QueryResult { - fn into(self) -> pb::QueryListItem { - pb::QueryListItem { - name: self.name, - description: self.description, - vm_status: self.status.to_string(), - trust_level: self.trust_level.to_string(), +impl From for pb::QueryListItem { + fn from(val: QueryResult) -> Self { + Self { + name: val.name, + description: val.description, + vm_status: val.status.to_string(), + trust_level: val.trust_level.to_string(), } } } @@ -117,12 +117,12 @@ impl TryFrom for Event { } } -impl Into for Event { - fn into(self) -> pb::WatchItem { - match self { - Event::UnitRegistered(value) => Self::watch_item(Status::Added(value.into())), - Event::UnitStatusChanged(value) => Self::watch_item(Status::Updated(value.into())), - Event::UnitShutdown(value) => Self::watch_item(Status::Removed(value.into())), +impl From for pb::WatchItem { + fn from(val: Event) -> Self { + match val { + Event::UnitRegistered(value) => Event::watch_item(Status::Added(value.into())), + Event::UnitStatusChanged(value) => Event::watch_item(Status::Updated(value.into())), + Event::UnitShutdown(value) => Event::watch_item(Status::Removed(value.into())), } } } diff --git a/common/src/types.rs b/common/src/types.rs index fc8afec..980c88f 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -4,7 +4,7 @@ use super::address::EndpointAddress; use crate::pb; use std::convert::{Into, TryFrom}; -use anyhow::{anyhow, bail}; +use anyhow::bail; use tokio_vsock::VsockAddr; #[derive(Debug, Copy, Clone, PartialEq)] @@ -107,30 +107,30 @@ impl TryFrom for UnitType { // so we can use just Into trait // FIXME: Combination of `UnitType{ vm: Host, service: VM}` is ILLEGAL!!! // Should we use TryInto, or fix type system? -impl Into for UnitType { - fn into(self) -> u32 { +impl From for u32 { + fn from(val: UnitType) -> Self { use ServiceType::*; use VmType::*; - match self.vm { - Host => match self.service { + match val.vm { + Host => match val.service { Mgr => 0, Svc => 1, App => 2, VM => 100500, }, - AdmVM => match self.service { + AdmVM => match val.service { VM => 3, Mgr => 4, Svc => 5, App => 6, }, - SysVM => match self.service { + SysVM => match val.service { VM => 7, Mgr => 8, Svc => 9, App => 10, }, - AppVM => match self.service { + AppVM => match val.service { VM => 11, Mgr => 12, Svc => 13, @@ -175,14 +175,14 @@ impl TryFrom for UnitStatus { } } -impl Into for UnitStatus { - fn into(self) -> pb::UnitStatus { - pb::UnitStatus { - name: self.name, - description: self.description, - load_state: self.load_state, - active_state: self.active_state, - path: self.path, +impl From for pb::UnitStatus { + fn from(val: UnitStatus) -> Self { + Self { + name: val.name, + description: val.description, + load_state: val.load_state, + active_state: val.active_state, + path: val.path, } } } @@ -217,32 +217,32 @@ impl TryFrom for EndpointEntry { } } -impl Into for EndpointEntry { - fn into(self) -> pb::TransportConfig { - match self.address { - EndpointAddress::Tcp { addr, port } => pb::TransportConfig { +impl From for pb::TransportConfig { + fn from(val: EndpointEntry) -> Self { + match val.address { + EndpointAddress::Tcp { addr, port } => Self { protocol: "tcp".into(), address: addr, port: port.to_string(), - name: self.tls_name, + name: val.tls_name, }, - EndpointAddress::Unix(unix) => pb::TransportConfig { + EndpointAddress::Unix(unix) => Self { protocol: "unix".into(), address: unix, port: "".into(), - name: self.tls_name, + name: val.tls_name, }, - EndpointAddress::Abstract(abstr) => pb::TransportConfig { + EndpointAddress::Abstract(abstr) => Self { protocol: "abstract".into(), address: abstr, port: "".into(), - name: self.tls_name, + name: val.tls_name, }, - EndpointAddress::Vsock(vs) => pb::TransportConfig { + EndpointAddress::Vsock(vs) => Self { protocol: "vsock".into(), address: vs.cid().to_string(), port: vs.port().to_string(), - name: self.tls_name, + name: val.tls_name, }, } } diff --git a/flake.nix b/flake.nix index f6fc4a1..ba1af79 100644 --- a/flake.nix +++ b/flake.nix @@ -66,13 +66,14 @@ ./internal ]; }; - in - rec { - givc-agent = pkgs.callPackage ./nixos/packages/givc-agent.nix { inherit src; }; givc-admin-rs = pkgs.callPackage ./nixos/packages/givc-admin-rs.nix { inherit crane; src = ./.; }; + in + { + inherit givc-admin-rs; + givc-agent = pkgs.callPackage ./nixos/packages/givc-agent.nix { inherit src; }; givc-cli = givc-admin-rs.cli; }; }; diff --git a/internal/cmd/givc-agent/main.go b/internal/cmd/givc-agent/main.go index beffde0..51464a9 100644 --- a/internal/cmd/givc-agent/main.go +++ b/internal/cmd/givc-agent/main.go @@ -27,7 +27,7 @@ import ( func main() { var err error - log.Infof("Executing %s", filepath.Base(os.Args[0])) + log.Infof("Running %s", filepath.Base(os.Args[0])) name := os.Getenv("NAME") if name == "" { diff --git a/src/admin/entry.rs b/src/admin/entry.rs index 1f4cec8..26f5277 100644 --- a/src/admin/entry.rs +++ b/src/admin/entry.rs @@ -2,7 +2,7 @@ // Some of them would be rewritten, replaced, or even removed use crate::pb; use anyhow::anyhow; -use std::convert::{Into, TryFrom}; +use std::convert::TryFrom; use givc_common::query::*; use givc_common::types::*; @@ -85,25 +85,25 @@ impl TryFrom for RegistryEntry { // Protocol very inconsistent here Ok(Self { name: req.name, - status: status, - watch: watch, + status, + watch, r#type: ty, placement: Placement::Endpoint(endpoint), }) } } -impl Into for RegistryEntry { - fn into(self) -> QueryResult { - let status = if self.status.is_running() { +impl From for QueryResult { + fn from(val: RegistryEntry) -> Self { + let status = if val.status.is_running() { VMStatus::Running } else { VMStatus::PoweredOff }; QueryResult { - name: self.name, - description: self.status.description, - status: status, + name: val.name, + description: val.status.description, + status, trust_level: TrustLevel::default(), } } diff --git a/src/admin/registry.rs b/src/admin/registry.rs index a1806d3..6cacab9 100644 --- a/src/admin/registry.rs +++ b/src/admin/registry.rs @@ -18,6 +18,12 @@ pub struct Registry { pubsub: broadcast::Sender, } +impl Default for Registry { + fn default() -> Self { + Self::new() + } +} + impl Registry { pub fn new() -> Self { Self { @@ -34,6 +40,7 @@ impl Registry { info!("Replaced old entry {:#?}", old); self.send_event(Event::UnitShutdown(old.into())) }; + info!("Sending event {event:?}"); self.send_event(event) } @@ -45,7 +52,10 @@ impl Registry { self.send_event(Event::UnitShutdown(entry.into())); Ok(()) } - None => bail!("Can't deregister entry {}, it not registered", name), + None => Err(anyhow!( + "Can't deregister entry {}, it not registered", + name + )), } } @@ -64,7 +74,7 @@ impl Registry { .filter(|x| x.starts_with(name)) .cloned() .collect(); - if list.len() == 0 { + if list.is_empty() { bail!("No entries match string {}", name) } else { Ok(list) @@ -115,7 +125,7 @@ impl Registry { e.status = status; self.send_event(Event::UnitStatusChanged(e.clone().into())) }) - .ok_or_else(|| anyhow!("Can't update state for {}, is not registered", name)) + .ok_or_else(|| anyhow!("Can't update state for {name}, is not registered")) } // FIXME: Should we dump full contents here for `query`/`query_list` high-level API @@ -156,7 +166,7 @@ mod tests { r.register(bar); assert!(r.contains(&foo_key)); - assert!(r.contains(&"bar".to_string())); + assert!(r.contains("bar")); let foo1 = r.by_name(&foo_key)?; assert_eq!(foo1, foo); diff --git a/src/admin/server.rs b/src/admin/server.rs index 9ea4286..b78ff5f 100644 --- a/src/admin/server.rs +++ b/src/admin/server.rs @@ -91,21 +91,21 @@ impl AdminServiceImpl { .agent() .with_context(|| "Resolving host agent".to_string())?; Ok(EndpointConfig { - transport: endpoint.into(), + transport: endpoint, tls: self.tls_config.clone(), }) } - pub fn agent_endpoint(&self, name: &String) -> anyhow::Result { - let endpoint = self.registry.by_name(&name)?.agent()?; + pub fn agent_endpoint(&self, name: &str) -> anyhow::Result { + let endpoint = self.registry.by_name(name)?.agent()?; Ok(EndpointConfig { - transport: endpoint.into(), + transport: endpoint, tls: self.tls_config.clone(), }) } pub fn app_entries(&self, name: String) -> anyhow::Result> { - if name.contains("@") { + if name.contains('@') { let list = self.registry.find_names(&name)?; Ok(list) } else { @@ -126,7 +126,7 @@ impl AdminServiceImpl { }; let tls_name = transport.tls_name.clone(); let endpoint = EndpointConfig { - transport: transport.into(), + transport, tls: self.tls_config.clone().map(|mut tls| { tls.tls_name = Some(tls_name); tls @@ -287,7 +287,7 @@ impl AdminServiceImpl { let app_entry = RegistryEntry { name: app_name, - status: status, + status, watch: true, r#type: UnitType { vm: VmType::AppVM, diff --git a/src/bin/givc-admin.rs b/src/bin/givc-admin.rs index 3731671..09a7668 100644 --- a/src/bin/givc-admin.rs +++ b/src/bin/givc-admin.rs @@ -71,7 +71,7 @@ async fn main() -> std::result::Result<(), Box> { let reflect = tonic_reflection::server::Builder::configure() .register_encoded_file_descriptor_set(ADMIN_DESCRIPTOR) - .build() + .build_v1() .unwrap(); let admin_service_svc = diff --git a/src/bin/givc-agent.rs b/src/bin/givc-agent.rs index e464209..a9291c2 100644 --- a/src/bin/givc-agent.rs +++ b/src/bin/givc-agent.rs @@ -131,7 +131,7 @@ async fn main() -> std::result::Result<(), Box> { let reflect = tonic_reflection::server::Builder::configure() .register_encoded_file_descriptor_set(SYSTEMD_DESCRIPTOR) - .build() + .build_v1() .unwrap(); let agent_service_svc = pb::systemd::unit_control_service_server::UnitControlServiceServer::new( diff --git a/src/bin/givc-cli.rs b/src/bin/givc-cli.rs index b7ff64d..7835fe2 100644 --- a/src/bin/givc-cli.rs +++ b/src/bin/givc-cli.rs @@ -1,8 +1,9 @@ +use anyhow::anyhow; use clap::{Parser, Subcommand}; use givc::endpoint::TlsConfig; use givc::types::*; use givc::utils::vsock::parse_vsock_addr; -use givc_client::AdminClient; +use givc_client::{client::WatchResult, AdminClient}; use givc_common::address::EndpointAddress; use serde::ser::Serialize; use std::path::PathBuf; @@ -173,11 +174,11 @@ async fn main() -> std::result::Result<(), Box> { None => None, }; let reply = admin.query(ty, by_name).await?; - dump(&reply, as_json)? + dump(reply, as_json)? } Commands::QueryList { as_json } => { let reply = admin.query_list().await?; - dump(&reply, as_json)? + dump(reply, as_json)? } Commands::SetLocale { locale } => { @@ -187,28 +188,33 @@ async fn main() -> std::result::Result<(), Box> { Commands::SetTimezone { timezone } => { admin.set_timezone(timezone).await?; } + Commands::Watch { as_json, limit, - initial, + initial: dump_initial, } => { - let watch = admin.watch().await?; - let mut limit = limit; - - if initial { - dump(watch.initial.clone(), as_json)? + let WatchResult { + initial, + channel, + mut task, + } = admin.watch().await?; + let mut limit = limit.map(|l| 0..l); + + if dump_initial { + dump(initial, as_json)? } - loop { - let event = watch.channel.recv().await?; - dump(event, as_json)?; - if limit.as_mut().is_some_and(|l| { - *l -= 1; - *l == 0 - }) { - break; - } - } + tokio::select! { + res = async move { + // Change to Option::is_none_or() with rust >1.82 + while !limit.as_mut().is_some_and(|l| l.next().is_none()) { + dump(channel.recv().await?, as_json)?; + } + Ok(()) + } => res, + _ = task.as_mut() => Err(anyhow!("Watch task stopped unexpectedly")) + }? } }; diff --git a/src/systemd_api/client.rs b/src/systemd_api/client.rs index a260dff..e67271a 100644 --- a/src/systemd_api/client.rs +++ b/src/systemd_api/client.rs @@ -81,7 +81,7 @@ impl SystemDClient { ) -> anyhow::Result { let request = pb::systemd::AppUnitRequest { unit_name: unit, - args: args, + args, }; let resp = self.connect().await?.start_application(request).await?; let status = resp.into_inner(); diff --git a/src/systemd_api/server.rs b/src/systemd_api/server.rs index 1bccce9..a81e6b1 100644 --- a/src/systemd_api/server.rs +++ b/src/systemd_api/server.rs @@ -4,12 +4,12 @@ use tonic::{Request, Response, Status}; pub use pb::systemd::unit_control_service_server::UnitControlServiceServer; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct SystemdService {} impl SystemdService { pub fn new() -> Self { - Self {} + Default::default() } } diff --git a/src/utils/auth.rs b/src/utils/auth.rs index 4472352..9b80549 100644 --- a/src/utils/auth.rs +++ b/src/utils/auth.rs @@ -48,7 +48,7 @@ pub fn ensure_host(req: Request, hostname: &str) -> Result<(), Status> { } } -pub fn ensure_hosts(req: Request, hostnames: &Vec<&str>) -> Result<(), Status> { +pub fn ensure_hosts(req: Request, hostnames: &[&str]) -> Result<(), Status> { let permit = req .extensions() .get::() diff --git a/src/utils/naming.rs b/src/utils/naming.rs index d368278..6bad690 100644 --- a/src/utils/naming.rs +++ b/src/utils/naming.rs @@ -25,7 +25,7 @@ pub fn parse_service_name(name: &str) -> anyhow::Result<&str> { // From `agent` code, ported for future pub fn parse_application_name(name: &str) -> anyhow::Result<(&str, i32)> { if let Some(name_no_suffix) = name.strip_suffix(".service") { - if let Some((left, right)) = name_no_suffix.rsplit_once("@") { + if let Some((left, right)) = name_no_suffix.rsplit_once('@') { let num = right .parse::() .with_context(|| format!("While parsing number part of {}", name))?; diff --git a/src/utils/vsock.rs b/src/utils/vsock.rs index c949072..5c40dc2 100644 --- a/src/utils/vsock.rs +++ b/src/utils/vsock.rs @@ -2,7 +2,7 @@ use anyhow::bail; use tokio_vsock::{VsockAddr, VMADDR_CID_HOST, VMADDR_CID_LOCAL}; pub fn parse_vsock_addr(addr: &str) -> anyhow::Result { - if let Some((cid, port)) = addr.split_once(":") { + if let Some((cid, port)) = addr.split_once(':') { let cid = match cid { "local" => VMADDR_CID_LOCAL, "host" => VMADDR_CID_HOST, From 99f7d0bed3d21c954ec271f0d6a93d4ff508e92d Mon Sep 17 00:00:00 2001 From: Santtu Lakkala Date: Tue, 1 Oct 2024 12:34:48 +0300 Subject: [PATCH 3/6] Refactor locale and timezone setting Change locale and timezone to be signalled from admin to agent after registration. Signed-off-by: Santtu Lakkala --- Cargo.lock | 19 +- Cargo.toml | 1 + api/admin/admin.pb.go | 204 ++++++++-------- api/admin/admin.proto | 3 +- api/locale/locale.pb.go | 271 +++++++++++++++++++++ api/locale/locale.proto | 21 ++ api/locale/locale_grpc.pb.go | 149 +++++++++++ api/protoc.sh | 7 +- client/src/client.rs | 7 +- common/build.rs | 5 + common/src/lib.rs | 3 + internal/cmd/givc-agent/main.go | 30 +-- internal/pkgs/localelistener/controller.go | 56 +++++ internal/pkgs/localelistener/transport.go | 65 +++++ src/admin/server.rs | 111 ++++++++- src/bin/givc-agent.rs | 20 +- 16 files changed, 804 insertions(+), 168 deletions(-) create mode 100644 api/locale/locale.pb.go create mode 100644 api/locale/locale.proto create mode 100644 api/locale/locale_grpc.pb.go create mode 100644 internal/pkgs/localelistener/controller.go create mode 100644 internal/pkgs/localelistener/transport.go diff --git a/Cargo.lock b/Cargo.lock index b9f1057..3932928 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -595,6 +595,7 @@ dependencies = [ "http-body 0.4.6", "hyper 0.14.30", "prost 0.12.6", + "regex", "serde", "serde_json", "strum 0.25.0", @@ -1326,14 +1327,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -1347,13 +1348,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -1364,9 +1365,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "ring" diff --git a/Cargo.toml b/Cargo.toml index c525f99..1af528a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ http = "0.2" http-body = "0.4.2" hyper = "0.14" prost = "0.12" +regex = "1.11" tokio = {version = "1.0", features = ["rt-multi-thread", "time", "macros", "fs"]} tokio-stream = "0.1" tokio-vsock = "0.5" diff --git a/api/admin/admin.pb.go b/api/admin/admin.pb.go index ba46fda..d99d0a4 100644 --- a/api/admin/admin.pb.go +++ b/api/admin/admin.pb.go @@ -257,8 +257,7 @@ type RegistryResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Timezone string `protobuf:"bytes,1,opt,name=Timezone,proto3" json:"Timezone,omitempty"` - Locale string `protobuf:"bytes,2,opt,name=Locale,proto3" json:"Locale,omitempty"` + Error *string `protobuf:"bytes,1,opt,name=Error,proto3,oneof" json:"Error,omitempty"` } func (x *RegistryResponse) Reset() { @@ -293,16 +292,9 @@ func (*RegistryResponse) Descriptor() ([]byte, []int) { return file_admin_proto_rawDescGZIP(), []int{3} } -func (x *RegistryResponse) GetTimezone() string { - if x != nil { - return x.Timezone - } - return "" -} - -func (x *RegistryResponse) GetLocale() string { - if x != nil { - return x.Locale +func (x *RegistryResponse) GetError() string { + if x != nil && x.Error != nil { + return *x.Error } return "" } @@ -816,104 +808,103 @@ var file_admin_proto_rawDesc = []byte{ 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x27, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x55, 0x6e, 0x69, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x05, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x22, 0x46, 0x0a, 0x10, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, - 0x65, 0x12, 0x16, 0x0a, 0x06, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x22, 0x6a, 0x0a, 0x12, 0x41, 0x70, 0x70, - 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x18, 0x0a, 0x07, 0x41, 0x70, 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x41, 0x70, 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x56, 0x6d, 0x4e, - 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x56, 0x6d, 0x4e, - 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x12, 0x0a, 0x04, 0x41, 0x72, 0x67, 0x73, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x41, 0x72, 0x67, 0x73, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x56, - 0x6d, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x51, 0x0a, 0x13, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, - 0x43, 0x6d, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x43, 0x6d, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x41, 0x70, - 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x41, - 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x22, 0x81, 0x01, 0x0a, 0x0d, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, - 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x44, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x56, 0x6d, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x56, 0x6d, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x54, 0x72, 0x75, 0x73, 0x74, 0x4c, 0x65, - 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x54, 0x72, 0x75, 0x73, 0x74, - 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x3d, 0x0a, 0x11, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, - 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x4c, 0x69, - 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, - 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, - 0x4c, 0x69, 0x73, 0x74, 0x22, 0xdd, 0x01, 0x0a, 0x09, 0x57, 0x61, 0x74, 0x63, 0x68, 0x49, 0x74, - 0x65, 0x6d, 0x12, 0x34, 0x0a, 0x07, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x51, 0x75, 0x65, 0x72, - 0x79, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, - 0x07, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x2c, 0x0a, 0x05, 0x41, 0x64, 0x64, 0x65, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x48, 0x00, 0x52, - 0x05, 0x41, 0x64, 0x64, 0x65, 0x64, 0x12, 0x30, 0x0a, 0x07, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x48, 0x00, 0x52, - 0x07, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x12, 0x30, 0x0a, 0x07, 0x52, 0x65, 0x6d, 0x6f, - 0x76, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x64, 0x6d, 0x69, - 0x6e, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x48, - 0x00, 0x52, 0x07, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x42, 0x08, 0x0a, 0x06, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x22, 0x27, 0x0a, 0x0d, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x22, 0x2d, 0x0a, - 0x0f, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1a, 0x0a, 0x08, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x32, 0xf9, 0x05, 0x0a, - 0x0c, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x44, 0x0a, - 0x0f, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x16, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, - 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x10, 0x53, 0x74, 0x61, 0x72, 0x74, 0x41, 0x70, 0x70, 0x6c, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, - 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x4b, 0x0a, 0x10, 0x50, 0x61, 0x75, 0x73, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x41, 0x70, 0x70, - 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1a, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, - 0x11, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, - 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0f, 0x53, - 0x74, 0x6f, 0x70, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, + 0x65, 0x22, 0x37, 0x0a, 0x10, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, + 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x6a, 0x0a, 0x12, 0x41, 0x70, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x18, 0x0a, 0x07, 0x41, 0x70, 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x41, 0x70, 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x56, 0x6d, + 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x56, 0x6d, + 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x12, 0x0a, 0x04, 0x41, 0x72, 0x67, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x41, 0x72, 0x67, 0x73, 0x42, 0x09, 0x0a, 0x07, 0x5f, + 0x56, 0x6d, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x51, 0x0a, 0x13, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, + 0x09, 0x43, 0x6d, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x43, 0x6d, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x41, + 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x41, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x22, 0x81, 0x01, 0x0a, 0x0d, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, 0x74, + 0x49, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x44, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x56, 0x6d, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x56, 0x6d, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x54, 0x72, 0x75, 0x73, 0x74, 0x4c, + 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x54, 0x72, 0x75, 0x73, + 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x3d, 0x0a, 0x11, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x4c, + 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x64, 0x6d, 0x69, + 0x6e, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, + 0x04, 0x4c, 0x69, 0x73, 0x74, 0x22, 0xdd, 0x01, 0x0a, 0x09, 0x57, 0x61, 0x74, 0x63, 0x68, 0x49, + 0x74, 0x65, 0x6d, 0x12, 0x34, 0x0a, 0x07, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, + 0x52, 0x07, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x2c, 0x0a, 0x05, 0x41, 0x64, 0x64, + 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, + 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x48, 0x00, + 0x52, 0x05, 0x41, 0x64, 0x64, 0x65, 0x64, 0x12, 0x30, 0x0a, 0x07, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, + 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x48, 0x00, + 0x52, 0x07, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x12, 0x30, 0x0a, 0x07, 0x52, 0x65, 0x6d, + 0x6f, 0x76, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x64, 0x6d, + 0x69, 0x6e, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, + 0x48, 0x00, 0x52, 0x07, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x42, 0x08, 0x0a, 0x06, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x27, 0x0a, 0x0d, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x22, 0x2d, + 0x0a, 0x0f, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x32, 0xf9, 0x05, + 0x0a, 0x0c, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x44, + 0x0a, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x16, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x64, 0x6d, 0x69, + 0x6e, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x10, 0x53, 0x74, 0x61, 0x72, 0x74, 0x41, 0x70, 0x70, + 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, + 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x4b, 0x0a, 0x10, 0x50, 0x61, 0x75, 0x73, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x41, 0x70, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1a, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, + 0x0a, 0x11, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x64, 0x6d, 0x69, - 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x09, 0x53, 0x65, 0x74, 0x4c, 0x6f, - 0x63, 0x61, 0x6c, 0x65, 0x12, 0x14, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x4c, 0x6f, 0x63, - 0x61, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x61, 0x64, 0x6d, - 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x0b, 0x53, 0x65, - 0x74, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x12, 0x16, 0x2e, 0x61, 0x64, 0x6d, 0x69, - 0x6e, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, - 0x00, 0x12, 0x28, 0x0a, 0x08, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x6f, 0x66, 0x66, 0x12, 0x0c, 0x2e, - 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x61, 0x64, - 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x26, 0x0a, 0x06, 0x52, - 0x65, 0x62, 0x6f, 0x6f, 0x74, 0x12, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x22, 0x00, 0x12, 0x27, 0x0a, 0x07, 0x53, 0x75, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x12, 0x0c, + 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0f, + 0x53, 0x74, 0x6f, 0x70, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x19, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x64, 0x6d, + 0x69, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x09, 0x53, 0x65, 0x74, 0x4c, + 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x12, 0x14, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x4c, 0x6f, + 0x63, 0x61, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x61, 0x64, + 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x0b, 0x53, + 0x65, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x12, 0x16, 0x2e, 0x61, 0x64, 0x6d, + 0x69, 0x6e, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x22, 0x00, 0x12, 0x28, 0x0a, 0x08, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x6f, 0x66, 0x66, 0x12, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x26, 0x0a, 0x06, - 0x57, 0x61, 0x6b, 0x65, 0x75, 0x70, 0x12, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, + 0x52, 0x65, 0x62, 0x6f, 0x6f, 0x74, 0x12, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x09, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, - 0x74, 0x12, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, - 0x18, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, 0x73, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2b, 0x0a, 0x05, 0x57, - 0x61, 0x74, 0x63, 0x68, 0x12, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x1a, 0x10, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, - 0x49, 0x74, 0x65, 0x6d, 0x22, 0x00, 0x30, 0x01, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x61, 0x64, - 0x6d, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x79, 0x22, 0x00, 0x12, 0x27, 0x0a, 0x07, 0x53, 0x75, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x12, + 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, + 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x26, 0x0a, + 0x06, 0x57, 0x61, 0x6b, 0x65, 0x75, 0x70, 0x12, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x09, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, + 0x73, 0x74, 0x12, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x1a, 0x18, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2b, 0x0a, 0x05, + 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, 0x0c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x1a, 0x10, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x57, 0x61, 0x74, 0x63, + 0x68, 0x49, 0x74, 0x65, 0x6d, 0x22, 0x00, 0x30, 0x01, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x61, + 0x64, 0x6d, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1135,6 +1126,7 @@ func file_admin_proto_init() { } } } + file_admin_proto_msgTypes[3].OneofWrappers = []interface{}{} file_admin_proto_msgTypes[4].OneofWrappers = []interface{}{} file_admin_proto_msgTypes[9].OneofWrappers = []interface{}{ (*WatchItem_Initial)(nil), diff --git a/api/admin/admin.proto b/api/admin/admin.proto index 035ad6a..c1057b0 100644 --- a/api/admin/admin.proto +++ b/api/admin/admin.proto @@ -28,8 +28,7 @@ message RegistryRequest { } message RegistryResponse { - string Timezone = 1; - string Locale = 2; + optional string Error = 1; } message ApplicationRequest { diff --git a/api/locale/locale.pb.go b/api/locale/locale.pb.go new file mode 100644 index 0000000..ef198e9 --- /dev/null +++ b/api/locale/locale.pb.go @@ -0,0 +1,271 @@ +// Copyright 2024 TII (SSRC) and the Ghaf contributors +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.1 +// protoc v4.24.4 +// source: locale.proto + +package locale + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type LocaleMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Locale string `protobuf:"bytes,1,opt,name=Locale,proto3" json:"Locale,omitempty"` +} + +func (x *LocaleMessage) Reset() { + *x = LocaleMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_locale_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LocaleMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LocaleMessage) ProtoMessage() {} + +func (x *LocaleMessage) ProtoReflect() protoreflect.Message { + mi := &file_locale_proto_msgTypes[0] + 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 LocaleMessage.ProtoReflect.Descriptor instead. +func (*LocaleMessage) Descriptor() ([]byte, []int) { + return file_locale_proto_rawDescGZIP(), []int{0} +} + +func (x *LocaleMessage) GetLocale() string { + if x != nil { + return x.Locale + } + return "" +} + +type TimezoneMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Timezone string `protobuf:"bytes,1,opt,name=Timezone,proto3" json:"Timezone,omitempty"` +} + +func (x *TimezoneMessage) Reset() { + *x = TimezoneMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_locale_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TimezoneMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TimezoneMessage) ProtoMessage() {} + +func (x *TimezoneMessage) ProtoReflect() protoreflect.Message { + mi := &file_locale_proto_msgTypes[1] + 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 TimezoneMessage.ProtoReflect.Descriptor instead. +func (*TimezoneMessage) Descriptor() ([]byte, []int) { + return file_locale_proto_rawDescGZIP(), []int{1} +} + +func (x *TimezoneMessage) GetTimezone() string { + if x != nil { + return x.Timezone + } + return "" +} + +type Empty struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *Empty) Reset() { + *x = Empty{} + if protoimpl.UnsafeEnabled { + mi := &file_locale_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Empty) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Empty) ProtoMessage() {} + +func (x *Empty) ProtoReflect() protoreflect.Message { + mi := &file_locale_proto_msgTypes[2] + 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 Empty.ProtoReflect.Descriptor instead. +func (*Empty) Descriptor() ([]byte, []int) { + return file_locale_proto_rawDescGZIP(), []int{2} +} + +var File_locale_proto protoreflect.FileDescriptor + +var file_locale_proto_rawDesc = []byte{ + 0x0a, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x22, 0x27, 0x0a, 0x0d, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x65, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x4c, 0x6f, 0x63, 0x61, 0x6c, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x22, + 0x2d, 0x0a, 0x0f, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x22, 0x07, + 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x32, 0x7c, 0x0a, 0x0c, 0x4c, 0x6f, 0x63, 0x61, 0x6c, + 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x33, 0x0a, 0x09, 0x4c, 0x6f, 0x63, 0x61, 0x6c, + 0x65, 0x53, 0x65, 0x74, 0x12, 0x15, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x4c, 0x6f, + 0x63, 0x61, 0x6c, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x0d, 0x2e, 0x6c, 0x6f, + 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x37, 0x0a, 0x0b, + 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x53, 0x65, 0x74, 0x12, 0x17, 0x2e, 0x6c, 0x6f, + 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x1a, 0x0d, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x22, 0x00, 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, + 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_locale_proto_rawDescOnce sync.Once + file_locale_proto_rawDescData = file_locale_proto_rawDesc +) + +func file_locale_proto_rawDescGZIP() []byte { + file_locale_proto_rawDescOnce.Do(func() { + file_locale_proto_rawDescData = protoimpl.X.CompressGZIP(file_locale_proto_rawDescData) + }) + return file_locale_proto_rawDescData +} + +var file_locale_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_locale_proto_goTypes = []interface{}{ + (*LocaleMessage)(nil), // 0: locale.LocaleMessage + (*TimezoneMessage)(nil), // 1: locale.TimezoneMessage + (*Empty)(nil), // 2: locale.Empty +} +var file_locale_proto_depIdxs = []int32{ + 0, // 0: locale.LocaleClient.LocaleSet:input_type -> locale.LocaleMessage + 1, // 1: locale.LocaleClient.TimezoneSet:input_type -> locale.TimezoneMessage + 2, // 2: locale.LocaleClient.LocaleSet:output_type -> locale.Empty + 2, // 3: locale.LocaleClient.TimezoneSet:output_type -> locale.Empty + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_locale_proto_init() } +func file_locale_proto_init() { + if File_locale_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_locale_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LocaleMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_locale_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TimezoneMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_locale_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Empty); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_locale_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_locale_proto_goTypes, + DependencyIndexes: file_locale_proto_depIdxs, + MessageInfos: file_locale_proto_msgTypes, + }.Build() + File_locale_proto = out.File + file_locale_proto_rawDesc = nil + file_locale_proto_goTypes = nil + file_locale_proto_depIdxs = nil +} diff --git a/api/locale/locale.proto b/api/locale/locale.proto new file mode 100644 index 0000000..d506c52 --- /dev/null +++ b/api/locale/locale.proto @@ -0,0 +1,21 @@ +// Copyright 2024 TII (SSRC) and the Ghaf contributors +// SPDX-License-Identifier: Apache-2.0 +syntax = "proto3"; +option go_package = "./locale"; +package locale; + +message LocaleMessage { + string Locale = 1; +} + +message TimezoneMessage { + string Timezone = 1; +} + +message Empty { +} + +service LocaleClient { + rpc LocaleSet(LocaleMessage) returns (Empty) {} + rpc TimezoneSet(TimezoneMessage) returns (Empty) {} +} diff --git a/api/locale/locale_grpc.pb.go b/api/locale/locale_grpc.pb.go new file mode 100644 index 0000000..f281560 --- /dev/null +++ b/api/locale/locale_grpc.pb.go @@ -0,0 +1,149 @@ +// Copyright 2024 TII (SSRC) and the Ghaf contributors +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc v4.24.4 +// source: locale.proto + +package locale + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + LocaleClient_LocaleSet_FullMethodName = "/locale.LocaleClient/LocaleSet" + LocaleClient_TimezoneSet_FullMethodName = "/locale.LocaleClient/TimezoneSet" +) + +// LocaleClientClient is the client API for LocaleClient service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type LocaleClientClient interface { + LocaleSet(ctx context.Context, in *LocaleMessage, opts ...grpc.CallOption) (*Empty, error) + TimezoneSet(ctx context.Context, in *TimezoneMessage, opts ...grpc.CallOption) (*Empty, error) +} + +type localeClientClient struct { + cc grpc.ClientConnInterface +} + +func NewLocaleClientClient(cc grpc.ClientConnInterface) LocaleClientClient { + return &localeClientClient{cc} +} + +func (c *localeClientClient) LocaleSet(ctx context.Context, in *LocaleMessage, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, LocaleClient_LocaleSet_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *localeClientClient) TimezoneSet(ctx context.Context, in *TimezoneMessage, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, LocaleClient_TimezoneSet_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// LocaleClientServer is the server API for LocaleClient service. +// All implementations must embed UnimplementedLocaleClientServer +// for forward compatibility +type LocaleClientServer interface { + LocaleSet(context.Context, *LocaleMessage) (*Empty, error) + TimezoneSet(context.Context, *TimezoneMessage) (*Empty, error) + mustEmbedUnimplementedLocaleClientServer() +} + +// UnimplementedLocaleClientServer must be embedded to have forward compatible implementations. +type UnimplementedLocaleClientServer struct { +} + +func (UnimplementedLocaleClientServer) LocaleSet(context.Context, *LocaleMessage) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method LocaleSet not implemented") +} +func (UnimplementedLocaleClientServer) TimezoneSet(context.Context, *TimezoneMessage) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method TimezoneSet not implemented") +} +func (UnimplementedLocaleClientServer) mustEmbedUnimplementedLocaleClientServer() {} + +// UnsafeLocaleClientServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to LocaleClientServer will +// result in compilation errors. +type UnsafeLocaleClientServer interface { + mustEmbedUnimplementedLocaleClientServer() +} + +func RegisterLocaleClientServer(s grpc.ServiceRegistrar, srv LocaleClientServer) { + s.RegisterService(&LocaleClient_ServiceDesc, srv) +} + +func _LocaleClient_LocaleSet_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(LocaleMessage) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LocaleClientServer).LocaleSet(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: LocaleClient_LocaleSet_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LocaleClientServer).LocaleSet(ctx, req.(*LocaleMessage)) + } + return interceptor(ctx, in, info, handler) +} + +func _LocaleClient_TimezoneSet_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TimezoneMessage) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LocaleClientServer).TimezoneSet(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: LocaleClient_TimezoneSet_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LocaleClientServer).TimezoneSet(ctx, req.(*TimezoneMessage)) + } + return interceptor(ctx, in, info, handler) +} + +// LocaleClient_ServiceDesc is the grpc.ServiceDesc for LocaleClient service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var LocaleClient_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "locale.LocaleClient", + HandlerType: (*LocaleClientServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "LocaleSet", + Handler: _LocaleClient_LocaleSet_Handler, + }, + { + MethodName: "TimezoneSet", + Handler: _LocaleClient_TimezoneSet_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "locale.proto", +} diff --git a/api/protoc.sh b/api/protoc.sh index 2d2bfa3..c2be2e2 100755 --- a/api/protoc.sh +++ b/api/protoc.sh @@ -7,7 +7,6 @@ gen_protoc() { "$1"/"$2" } -gen_protoc api/admin admin.proto -gen_protoc api/systemd systemd.proto -gen_protoc api/wifi wifi.proto -gen_protoc api/hwid hwid.proto +for api in admin systemd wifi hwid locale; do + gen_protoc api/$api $api.proto +done diff --git a/client/src/client.rs b/client/src/client.rs index 855818d..fe54944 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -63,7 +63,7 @@ impl AdminClient { ty: UnitType, endpoint: EndpointEntry, status: UnitStatus, - ) -> anyhow::Result<(String, String)> { + ) -> anyhow::Result<()> { // Convert everything into wire format let request = pb::admin::RegistryRequest { name, @@ -78,7 +78,10 @@ impl AdminClient { .register_service(request) .await? .into_inner(); - Ok((response.timezone, response.locale)) + if let Some(err) = response.error { + bail!("{err}"); + } + Ok(()) } pub async fn start( diff --git a/common/build.rs b/common/build.rs index 3e967cc..f76b44f 100644 --- a/common/build.rs +++ b/common/build.rs @@ -8,6 +8,11 @@ fn main() { .compile(&["api/admin/admin.proto"], &["admin"]) .unwrap(); + tonic_build::configure() + .file_descriptor_set_path(out_dir.join("locale_descriptor.bin")) + .compile(&["api/locale/locale.proto"], &["locale"]) + .unwrap(); + tonic_build::configure() .file_descriptor_set_path(out_dir.join("systemd_descriptor.bin")) .compile(&["api/systemd/systemd.proto"], &["systemd"]) diff --git a/common/src/lib.rs b/common/src/lib.rs index 9209e20..240dc0e 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -6,6 +6,9 @@ pub mod pb { pub mod admin { tonic::include_proto!("admin"); } + pub mod locale { + tonic::include_proto!("locale"); + } pub mod systemd { tonic::include_proto!("systemd"); } diff --git a/internal/cmd/givc-agent/main.go b/internal/cmd/givc-agent/main.go index 51464a9..7cb81e5 100644 --- a/internal/cmd/givc-agent/main.go +++ b/internal/cmd/givc-agent/main.go @@ -6,7 +6,6 @@ import ( "context" "crypto/tls" "os" - "os/exec" "path/filepath" "strconv" "strings" @@ -15,6 +14,7 @@ import ( givc_app "givc/internal/pkgs/applications" givc_grpc "givc/internal/pkgs/grpc" "givc/internal/pkgs/hwidmanager" + "givc/internal/pkgs/localelistener" "givc/internal/pkgs/serviceclient" "givc/internal/pkgs/servicemanager" "givc/internal/pkgs/types" @@ -177,29 +177,9 @@ func main() { <-serverStarted // Register agent - response, err := serviceclient.RegisterRemoteService(cfgAdminServer, agentEntryRequest) + _, err := serviceclient.RegisterRemoteService(cfgAdminServer, agentEntryRequest) if err != nil { log.Fatalf("Error register agent: %s", err) - } else { - if response.Locale != "" { - if err := exec.Command("localectl", "set-locale", response.Locale).Run(); err != nil { - log.Warningf("Failed to set locale: %s", err) - } - if givc_util.IsRoot() { - if err := exec.Command("systemctl", "set-environment", "LANG="+response.Locale).Run(); err != nil { - log.Warningf("Failed to set environment: %s", err) - } - } else { - if err := exec.Command("systemctl", "--user", "set-environment", "LANG="+response.Locale).Run(); err != nil { - log.Warningf("Failed to set environment: %s", err) - } - } - } - if response.Timezone != "" { - if err := exec.Command("timedatectl", "set-timezone", response.Timezone).Run(); err != nil { - log.Warningf("Failed to set timezone: %s", err) - } - } } // Register services @@ -238,6 +218,12 @@ func main() { } grpcServices = append(grpcServices, systemdControlServer) + localeClientServer, err := localelistener.NewLocaleServer() + if err != nil { + log.Fatalf("Cannot create locale listener server") + } + grpcServices = append(grpcServices, localeClientServer) + if wifiEnabled { // Create wifi control server wifiControlServer, err := wifimanager.NewWifiControlServer() diff --git a/internal/pkgs/localelistener/controller.go b/internal/pkgs/localelistener/controller.go new file mode 100644 index 0000000..66b61a4 --- /dev/null +++ b/internal/pkgs/localelistener/controller.go @@ -0,0 +1,56 @@ +// Copyright 2024 TII (SSRC) and the Ghaf contributors +// SPDX-License-Identifier: Apache-2.0 +package localelistener + +import ( + "context" + "fmt" + givc_util "givc/internal/pkgs/utility" + "os/exec" + + log "github.com/sirupsen/logrus" +) + +type LocaleController struct { +} + +func NewController() (*LocaleController, error) { + return &LocaleController{}, nil +} + +func (c *LocaleController) SetLocale(ctx context.Context, locale string) error { + + // Input validation + if ctx == nil { + return fmt.Errorf("context cannot be nil") + } + + if err := exec.Command("localectl", "set-locale", locale).Run(); err != nil { + log.Warningf("Failed to set locale: %s", err) + } + if givc_util.IsRoot() { + if err := exec.Command("systemctl", "set-environment", "LANG="+locale).Run(); err != nil { + log.Warningf("Failed to set environment: %s", err) + } + } else { + if err := exec.Command("systemctl", "--user", "set-environment", "LANG="+locale).Run(); err != nil { + log.Warningf("Failed to set environment: %s", err) + } + } + + return nil +} + +func (c *LocaleController) SetTimezone(ctx context.Context, timezone string) error { + + // Input validation + if ctx == nil { + return fmt.Errorf("context cannot be nil") + } + + if err := exec.Command("timedatectl", "set-timezone", timezone).Run(); err != nil { + log.Warningf("Failed to set timezone: %s", err) + } + + return nil +} diff --git a/internal/pkgs/localelistener/transport.go b/internal/pkgs/localelistener/transport.go new file mode 100644 index 0000000..6a8533a --- /dev/null +++ b/internal/pkgs/localelistener/transport.go @@ -0,0 +1,65 @@ +// Copyright 2024 TII (SSRC) and the Ghaf contributors +// SPDX-License-Identifier: Apache-2.0 +package localelistener + +import ( + "context" + "fmt" + + locale_api "givc/api/locale" + + log "github.com/sirupsen/logrus" + "google.golang.org/grpc" +) + +type LocaleServer struct { + Controller *LocaleController + locale_api.UnimplementedLocaleClientServer +} + +func (s *LocaleServer) Name() string { + return "Locale listener" +} + +func (s *LocaleServer) RegisterGrpcService(srv *grpc.Server) { + locale_api.RegisterLocaleClientServer(srv, s) +} + +func NewLocaleServer() (*LocaleServer, error) { + + localeController, err := NewController() + if err != nil { + log.Errorf("Error creating locale controller: %v", err) + return nil, err + } + + localeServer := LocaleServer{ + Controller: localeController, + } + + return &localeServer, nil +} + +func (s *LocaleServer) LocaleSet(ctx context.Context, req *locale_api.LocaleMessage) (*locale_api.Empty, error) { + log.Infof("Incoming notification of changes locale\n") + + err := s.Controller.SetLocale(context.Background(), req.Locale) + if err != nil { + log.Infof("[SetLocale] Error setting locale: %v\n", err) + return nil, fmt.Errorf("Cannot set locale") + } + + return &locale_api.Empty{}, nil +} + +func (s *LocaleServer) TimezoneSet(ctx context.Context, req *locale_api.TimezoneMessage) (*locale_api.Empty, error) { + log.Infof("Incoming notification of set timezone\n") + + err := s.Controller.SetTimezone(context.Background(), req.Timezone) + if err != nil { + log.Infof("[SetLocale] Error setting timezone: %v\n", err) + return nil, fmt.Errorf("Cannot set timezone") + } + + return &locale_api.Empty{}, nil +} diff --git a/src/admin/server.rs b/src/admin/server.rs index b78ff5f..0a7383b 100644 --- a/src/admin/server.rs +++ b/src/admin/server.rs @@ -3,6 +3,7 @@ use crate::pb::{self, *}; use anyhow::{bail, Context}; use async_stream::try_stream; use givc_common::query::Event; +use regex::Regex; use std::pin::Pin; use std::sync::Arc; use std::time::Duration; @@ -47,6 +48,22 @@ pub struct AdminService { inner: Arc, } +struct Validator(); + +impl Validator { + pub fn validate_locale(locale: &str) -> bool { + let validator = Regex::new( + r"^(?:C|POSIX|[a-z]{2}(?:_[A-Z]{2})?(?:@[a-zA-Z0-9]+)?)(?:\.[-a-zA-Z0-9]+)?$", + ) + .unwrap(); + validator.is_match(locale) + } + pub fn validate_timezone(timezone: &str) -> bool { + let validator = Regex::new(r"^[A-Z][-+a-zA-Z0-9]*(?:/[A-Z][-+a-zA-Z0-9_]*)*$").unwrap(); + validator.is_match(timezone) + } +} + impl AdminService { pub fn new(use_tls: Option) -> Self { let inner = Arc::new(AdminServiceImpl::new(use_tls)); @@ -323,12 +340,38 @@ impl pb::admin_service_server::AdminService for AdminService { info!("Registering service {:?}", req); let entry = RegistryEntry::try_from(req) .map_err(|e| Status::new(Code::InvalidArgument, format!("{e}")))?; + let mut notify = None; + + if matches!( + entry.r#type, + UnitType { + service: ServiceType::Mgr, + .. + } + ) { + notify = Some(entry.name.to_owned()); + } self.inner.register(entry); - let res = RegistryResponse { - timezone: self.inner.timezone.lock().await.clone(), - locale: self.inner.locale.lock().await.clone(), - }; + let res = RegistryResponse { error: None }; + + if let Some(name) = notify { + if let Ok(endpoint) = self.inner.agent_endpoint(&name) { + let locale = self.inner.locale.lock().await.clone(); + let timezone = self.inner.timezone.lock().await.clone(); + tokio::spawn(async move { + if let Ok(conn) = endpoint.connect().await { + let mut client = + pb::locale::locale_client_client::LocaleClientClient::new(conn); + let localemsg = pb::locale::LocaleMessage { locale }; + let _ = client.locale_set(localemsg).await; + + let timezonemsg = pb::locale::TimezoneMessage { timezone }; + let _ = client.timezone_set(timezonemsg).await; + } + }); + } + } info!("Responding with {res:?}"); Ok(Response::new(res)) } @@ -458,6 +501,9 @@ impl pb::admin_service_server::AdminService for AdminService { request: tonic::Request, ) -> std::result::Result, tonic::Status> { escalate(request, |req| async move { + if !Validator::validate_locale(&req.locale) { + bail!("Invalid locale"); + } let _ = tokio::fs::write(LOCALE_CONF, format!("LANG={}", req.locale)).await; *self.inner.locale.lock().await = req.locale; Ok(Empty {}) @@ -470,6 +516,9 @@ impl pb::admin_service_server::AdminService for AdminService { request: tonic::Request, ) -> std::result::Result, tonic::Status> { escalate(request, |req| async move { + if !Validator::validate_timezone(&req.timezone) { + bail!("Invalid timezone"); + } let _ = tokio::fs::write(TIMEZONE_CONF, &req.timezone).await; *self.inner.timezone.lock().await = req.timezone; Ok(Empty {}) @@ -505,3 +554,57 @@ impl pb::admin_service_server::AdminService for AdminService { .await } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_locale_validator() -> anyhow::Result<()> { + if ![ + "en_US.UTF-8", + "C", + "POSIX", + "C.UTF-8", + "ar_AE.UTF-8", + "fi_FI@euro.UTF-8", + "fi_FI@euro", + ] + .into_iter() + .all(Validator::validate_locale) + { + bail!("Valid locale rejected"); + } + if ["`rm -Rf --no-preserve-root /`", "; whoami", "iwaenfli"] + .into_iter() + .any(Validator::validate_locale) + { + bail!("Invalid locale accepted"); + } + Ok(()) + } + + #[test] + fn test_timezone_validator() -> anyhow::Result<()> { + if ![ + "UTC", + "Europe/Helsinki", + "Asia/Abu_Dhabi", + "Etc/GMT+8", + "GMT-0", + "America/Argentina/Rio_Gallegos", + ] + .into_iter() + .all(Validator::validate_timezone) + { + bail!("Valid timezone rejected"); + } + if ["/foobar", "`whoami`", "Almost//Valid"] + .into_iter() + .any(Validator::validate_timezone) + { + bail!("Invalid timezone accepted"); + } + Ok(()) + } +} diff --git a/src/bin/givc-agent.rs b/src/bin/givc-agent.rs index a9291c2..8b91898 100644 --- a/src/bin/givc-agent.rs +++ b/src/bin/givc-agent.rs @@ -9,7 +9,6 @@ use givc_common::pb; use givc_common::pb::reflection::SYSTEMD_DESCRIPTOR; use std::net::SocketAddr; use std::path::PathBuf; -use std::process::{Command, Stdio}; use tonic::transport::Server; use tracing::info; @@ -108,27 +107,10 @@ async fn main() -> std::result::Result<(), Box> { let admin_tls = tls.clone().map(|tls| (cli.admin_server_name, tls)); let admin = AdminClient::new(cli.admin_server_addr, cli.admin_server_port, admin_tls); - let (timezone, locale) = admin + admin .register_service(agent_service_name, cli.r#type.try_into()?, endpoint, status) .await?; - if !timezone.is_empty() { - Command::new("timedatectl") - .args(["set-timezone", timezone.as_str()]) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn()?; - } - if !locale.is_empty() { - Command::new("localectl") - .args(["set-locale", locale.as_str()]) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn()?; - } - let reflect = tonic_reflection::server::Builder::configure() .register_encoded_file_descriptor_set(SYSTEMD_DESCRIPTOR) .build_v1() From c7894e9ee7dd821f2832062383d6cb92c32a2c60 Mon Sep 17 00:00:00 2001 From: Santtu Lakkala Date: Tue, 1 Oct 2024 13:38:02 +0300 Subject: [PATCH 4/6] Revert client API changes Signed-off-by: Santtu Lakkala --- client/src/client.rs | 9 ++------- src/bin/givc-cli.rs | 21 +++++---------------- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/client/src/client.rs b/client/src/client.rs index fe54944..d096329 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -2,8 +2,6 @@ use anyhow::bail; use async_channel::Receiver; use givc_common::pb; pub use givc_common::query::{Event, QueryResult}; -use std::future::Future; -use std::pin::Pin; use tokio_stream::StreamExt; use tonic::transport::Channel; use tracing::debug; @@ -20,8 +18,6 @@ pub struct WatchResult { // Design defence: we use `async-channel` here, as it could be used with both // tokio's and glib's eventloop, and recommended by gtk4-rs developers: pub channel: Receiver, - - pub task: Pin>>, } #[derive(Debug)] @@ -184,7 +180,7 @@ impl AdminClient { None => bail!("Protocol error, status field missing"), }; - let task = async move { + tokio::spawn(async move { loop { if let Ok(Some(event)) = watch.try_next().await { let event = match Event::try_from(event) { @@ -203,12 +199,11 @@ impl AdminClient { break; } } - }; + }); let result = WatchResult { initial: list, channel: rx, - task: Box::pin(task), }; Ok(result) } diff --git a/src/bin/givc-cli.rs b/src/bin/givc-cli.rs index 7835fe2..7e9d728 100644 --- a/src/bin/givc-cli.rs +++ b/src/bin/givc-cli.rs @@ -1,4 +1,3 @@ -use anyhow::anyhow; use clap::{Parser, Subcommand}; use givc::endpoint::TlsConfig; use givc::types::*; @@ -194,27 +193,17 @@ async fn main() -> std::result::Result<(), Box> { limit, initial: dump_initial, } => { - let WatchResult { - initial, - channel, - mut task, - } = admin.watch().await?; + let WatchResult { initial, channel } = admin.watch().await?; let mut limit = limit.map(|l| 0..l); if dump_initial { dump(initial, as_json)? } - tokio::select! { - res = async move { - // Change to Option::is_none_or() with rust >1.82 - while !limit.as_mut().is_some_and(|l| l.next().is_none()) { - dump(channel.recv().await?, as_json)?; - } - Ok(()) - } => res, - _ = task.as_mut() => Err(anyhow!("Watch task stopped unexpectedly")) - }? + // Change to Option::is_none_or() with rust >1.82 + while !limit.as_mut().is_some_and(|l| l.next().is_none()) { + dump(channel.recv().await?, as_json)?; + } } }; From 719cc37d58c9eac4eeb02ef9424a0476cf3354f6 Mon Sep 17 00:00:00 2001 From: Santtu Lakkala Date: Tue, 1 Oct 2024 15:55:35 +0300 Subject: [PATCH 5/6] Implement locale and timezone notifications Signed-off-by: Santtu Lakkala --- common/src/query.rs | 4 +-- src/admin/registry.rs | 5 ++++ src/admin/server.rs | 60 +++++++++++++++++++++++++++++++++---------- src/utils/naming.rs | 2 +- 4 files changed, 55 insertions(+), 16 deletions(-) diff --git a/common/src/query.rs b/common/src/query.rs index e96897f..f107cea 100644 --- a/common/src/query.rs +++ b/common/src/query.rs @@ -48,9 +48,9 @@ impl TryFrom for QueryResult { name: item.name, description: item.description, status: VMStatus::from_str(item.vm_status.as_str()) - .context(format!("While parsing vm_status {}", &item.vm_status))?, + .with_context(|| format!("While parsing vm_status {}", item.vm_status))?, trust_level: TrustLevel::from_str(item.trust_level.as_str()) - .context(format!("While parsing trust_level {}", &item.trust_level))?, + .with_context(|| format!("While parsing trust_level {}", item.trust_level))?, }) } } diff --git a/src/admin/registry.rs b/src/admin/registry.rs index 6cacab9..6b1cba7 100644 --- a/src/admin/registry.rs +++ b/src/admin/registry.rs @@ -81,6 +81,11 @@ impl Registry { } } + pub fn find_map Option>(&self, filter: F) -> Vec { + let state = self.map.lock().unwrap(); + state.values().filter_map(filter).collect() + } + pub fn by_type_many(&self, ty: UnitType) -> Vec { let state = self.map.lock().unwrap(); state.values().filter(|x| x.r#type == ty).cloned().collect() diff --git a/src/admin/server.rs b/src/admin/server.rs index 0a7383b..5faae1b 100644 --- a/src/admin/server.rs +++ b/src/admin/server.rs @@ -104,22 +104,19 @@ impl AdminServiceImpl { vm: VmType::Host, service: ServiceType::Mgr, })?; - let endpoint = host_mgr - .agent() - .with_context(|| "Resolving host agent".to_string())?; - Ok(EndpointConfig { - transport: endpoint, - tls: self.tls_config.clone(), - }) + self.endpoint(host_mgr).context("Resolving host agent") } - pub fn agent_endpoint(&self, name: &str) -> anyhow::Result { - let endpoint = self.registry.by_name(name)?.agent()?; + pub fn endpoint(&self, reentry: RegistryEntry) -> anyhow::Result { Ok(EndpointConfig { - transport: endpoint, + transport: reentry.agent()?, tls: self.tls_config.clone(), }) } + pub fn agent_endpoint(&self, name: &str) -> anyhow::Result { + let reentry = self.registry.by_name(name)?; + self.endpoint(reentry) + } pub fn app_entries(&self, name: String) -> anyhow::Result> { if name.contains('@') { @@ -206,7 +203,7 @@ impl AdminServiceImpl { let name = parse_service_name(&entry.name)?; self.start_vm(name) .await - .with_context(|| format!("handing error, by restart VM {}", &entry.name))?; + .with_context(|| format!("handing error, by restart VM {}", entry.name))?; Ok(()) // FIXME: should use `?` from line above, why it didn't work? } (x, y) => bail!( @@ -245,7 +242,7 @@ impl AdminServiceImpl { if inactive { self.handle_error(entry) .await - .with_context(|| "during handle error")? + .context("during handle error")? } } } @@ -287,7 +284,7 @@ impl AdminServiceImpl { Err(_) => { self.start_vm(&vm_name) .await - .context(format!("Starting vm for {}", &name))?; + .with_context(|| format!("Starting vm for {name}"))?; self.registry .by_name(&systemd_agent) .context("after starting VM")? @@ -505,7 +502,26 @@ impl pb::admin_service_server::AdminService for AdminService { bail!("Invalid locale"); } let _ = tokio::fs::write(LOCALE_CONF, format!("LANG={}", req.locale)).await; + let managers = self.inner.registry.find_map(|re| { + (re.r#type.service == ServiceType::Mgr) + .then_some(()) + .and_then(|_| self.inner.endpoint(re.clone()).ok()) + }); + let locale = req.locale.clone(); + tokio::spawn(async move { + for ec in managers { + if let Ok(conn) = ec.connect().await { + let mut client = + pb::locale::locale_client_client::LocaleClientClient::new(conn); + let localemsg = pb::locale::LocaleMessage { + locale: locale.clone(), + }; + let _ = client.locale_set(localemsg).await; + } + } + }); *self.inner.locale.lock().await = req.locale; + Ok(Empty {}) }) .await @@ -520,6 +536,24 @@ impl pb::admin_service_server::AdminService for AdminService { bail!("Invalid timezone"); } let _ = tokio::fs::write(TIMEZONE_CONF, &req.timezone).await; + let managers = self.inner.registry.find_map(|re| { + (re.r#type.service == ServiceType::Mgr) + .then_some(()) + .and_then(|_| self.inner.endpoint(re.clone()).ok()) + }); + let timezone = req.timezone.clone(); + tokio::spawn(async move { + for ec in managers { + if let Ok(conn) = ec.connect().await { + let mut client = + pb::locale::locale_client_client::LocaleClientClient::new(conn); + let tzmsg = pb::locale::TimezoneMessage { + timezone: timezone.clone(), + }; + let _ = client.timezone_set(tzmsg).await; + } + } + }); *self.inner.timezone.lock().await = req.timezone; Ok(Empty {}) }) diff --git a/src/utils/naming.rs b/src/utils/naming.rs index 6bad690..f884b6e 100644 --- a/src/utils/naming.rs +++ b/src/utils/naming.rs @@ -28,7 +28,7 @@ pub fn parse_application_name(name: &str) -> anyhow::Result<(&str, i32)> { if let Some((left, right)) = name_no_suffix.rsplit_once('@') { let num = right .parse::() - .with_context(|| format!("While parsing number part of {}", name))?; + .with_context(|| format!("While parsing number part of {name}"))?; return Ok((left, num)); } }; From e2ceb07b67014e6b191af4b581a128f32eeea98b Mon Sep 17 00:00:00 2001 From: Santtu Lakkala Date: Wed, 2 Oct 2024 16:48:26 +0300 Subject: [PATCH 6/6] Review changes Signed-off-by: Santtu Lakkala --- client/src/client.rs | 42 ++++++++++++++++++++++++++---------------- src/bin/givc-cli.rs | 8 ++++---- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/client/src/client.rs b/client/src/client.rs index d096329..83725ef 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -1,12 +1,13 @@ use anyhow::bail; use async_channel::Receiver; -use givc_common::pb; -pub use givc_common::query::{Event, QueryResult}; +use tokio::sync::mpsc; use tokio_stream::StreamExt; use tonic::transport::Channel; use tracing::debug; use givc_common::address::EndpointAddress; +use givc_common::pb; +pub use givc_common::query::{Event, QueryResult}; use givc_common::types::*; use crate::endpoint::{EndpointConfig, TlsConfig}; @@ -18,6 +19,8 @@ pub struct WatchResult { // Design defence: we use `async-channel` here, as it could be used with both // tokio's and glib's eventloop, and recommended by gtk4-rs developers: pub channel: Receiver, + + _quit: mpsc::Sender<()>, } #[derive(Debug)] @@ -165,6 +168,7 @@ impl AdminClient { use pb::admin::watch_item::Status; use pb::admin::WatchItem; let (tx, rx) = async_channel::bounded::(10); + let (quittx, mut quitrx) = mpsc::channel(1); let mut watch = self .connect_to() @@ -181,29 +185,35 @@ impl AdminClient { }; tokio::spawn(async move { - loop { - if let Ok(Some(event)) = watch.try_next().await { - let event = match Event::try_from(event) { - Ok(event) => event, - Err(e) => { - debug!("Fail to decode: {e}"); + tokio::select! { + _ = async move { + loop { + if let Ok(Some(event)) = watch.try_next().await { + let event = match Event::try_from(event) { + Ok(event) => event, + Err(e) => { + debug!("Fail to decode: {e}"); + break; + } + }; + if let Err(e) = tx.send(event).await { + debug!("Fail to send event: {e}"); + break; + } + } else { + debug!("Stream closed by server"); break; } - }; - if let Err(e) = tx.send(event).await { - debug!("Fail to send event: {e}"); - break; } - } else { - debug!("Stream closed by server"); - break; - } + } => {} + _ = quitrx.recv() => {} } }); let result = WatchResult { initial: list, channel: rx, + _quit: quittx, }; Ok(result) } diff --git a/src/bin/givc-cli.rs b/src/bin/givc-cli.rs index 7e9d728..46483be 100644 --- a/src/bin/givc-cli.rs +++ b/src/bin/givc-cli.rs @@ -2,7 +2,7 @@ use clap::{Parser, Subcommand}; use givc::endpoint::TlsConfig; use givc::types::*; use givc::utils::vsock::parse_vsock_addr; -use givc_client::{client::WatchResult, AdminClient}; +use givc_client::client::AdminClient; use givc_common::address::EndpointAddress; use serde::ser::Serialize; use std::path::PathBuf; @@ -193,16 +193,16 @@ async fn main() -> std::result::Result<(), Box> { limit, initial: dump_initial, } => { - let WatchResult { initial, channel } = admin.watch().await?; + let watch = admin.watch().await?; let mut limit = limit.map(|l| 0..l); if dump_initial { - dump(initial, as_json)? + dump(watch.initial, as_json)? } // Change to Option::is_none_or() with rust >1.82 while !limit.as_mut().is_some_and(|l| l.next().is_none()) { - dump(channel.recv().await?, as_json)?; + dump(watch.channel.recv().await?, as_json)?; } } };