diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/HomeOps-Hub.iml b/.idea/HomeOps-Hub.iml new file mode 100644 index 0000000..7ee078d --- /dev/null +++ b/.idea/HomeOps-Hub.iml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/go.imports.xml b/.idea/go.imports.xml new file mode 100644 index 0000000..d7202f0 --- /dev/null +++ b/.idea/go.imports.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/api/gen/homeops/hub.pb.go b/api/gen/homeops/hub.pb.go new file mode 100644 index 0000000..5f6836c --- /dev/null +++ b/api/gen/homeops/hub.pb.go @@ -0,0 +1,328 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v7.34.1 +// source: homeops/hub.proto + +package homeops + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +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 PongResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Pong string `protobuf:"bytes,1,opt,name=pong,proto3" json:"pong,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PongResponse) Reset() { + *x = PongResponse{} + mi := &file_homeops_hub_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PongResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PongResponse) ProtoMessage() {} + +func (x *PongResponse) ProtoReflect() protoreflect.Message { + mi := &file_homeops_hub_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PongResponse.ProtoReflect.Descriptor instead. +func (*PongResponse) Descriptor() ([]byte, []int) { + return file_homeops_hub_proto_rawDescGZIP(), []int{0} +} + +func (x *PongResponse) GetPong() string { + if x != nil { + return x.Pong + } + return "" +} + +type RegisterAgentRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + AgentName string `protobuf:"bytes,2,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` + Hostname string `protobuf:"bytes,3,opt,name=hostname,proto3" json:"hostname,omitempty"` + Version string `protobuf:"bytes,4,opt,name=version,proto3" json:"version,omitempty"` + Arch string `protobuf:"bytes,5,opt,name=arch,proto3" json:"arch,omitempty"` + Config *AgentConfig `protobuf:"bytes,6,opt,name=config,proto3" json:"config,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RegisterAgentRequest) Reset() { + *x = RegisterAgentRequest{} + mi := &file_homeops_hub_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RegisterAgentRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterAgentRequest) ProtoMessage() {} + +func (x *RegisterAgentRequest) ProtoReflect() protoreflect.Message { + mi := &file_homeops_hub_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterAgentRequest.ProtoReflect.Descriptor instead. +func (*RegisterAgentRequest) Descriptor() ([]byte, []int) { + return file_homeops_hub_proto_rawDescGZIP(), []int{1} +} + +func (x *RegisterAgentRequest) GetAgentId() string { + if x != nil { + return x.AgentId + } + return "" +} + +func (x *RegisterAgentRequest) GetAgentName() string { + if x != nil { + return x.AgentName + } + return "" +} + +func (x *RegisterAgentRequest) GetHostname() string { + if x != nil { + return x.Hostname + } + return "" +} + +func (x *RegisterAgentRequest) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *RegisterAgentRequest) GetArch() string { + if x != nil { + return x.Arch + } + return "" +} + +func (x *RegisterAgentRequest) GetConfig() *AgentConfig { + if x != nil { + return x.Config + } + return nil +} + +type AgentConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + System string `protobuf:"bytes,1,opt,name=system,proto3" json:"system,omitempty"` + Docker bool `protobuf:"varint,2,opt,name=docker,proto3" json:"docker,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentConfig) Reset() { + *x = AgentConfig{} + mi := &file_homeops_hub_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentConfig) ProtoMessage() {} + +func (x *AgentConfig) ProtoReflect() protoreflect.Message { + mi := &file_homeops_hub_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentConfig.ProtoReflect.Descriptor instead. +func (*AgentConfig) Descriptor() ([]byte, []int) { + return file_homeops_hub_proto_rawDescGZIP(), []int{2} +} + +func (x *AgentConfig) GetSystem() string { + if x != nil { + return x.System + } + return "" +} + +func (x *AgentConfig) GetDocker() bool { + if x != nil { + return x.Docker + } + return false +} + +type RegisterAgentResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + HeartbeatIntervalSecond int64 `protobuf:"varint,1,opt,name=heartbeat_interval_second,json=heartbeatIntervalSecond,proto3" json:"heartbeat_interval_second,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RegisterAgentResponse) Reset() { + *x = RegisterAgentResponse{} + mi := &file_homeops_hub_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RegisterAgentResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterAgentResponse) ProtoMessage() {} + +func (x *RegisterAgentResponse) ProtoReflect() protoreflect.Message { + mi := &file_homeops_hub_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterAgentResponse.ProtoReflect.Descriptor instead. +func (*RegisterAgentResponse) Descriptor() ([]byte, []int) { + return file_homeops_hub_proto_rawDescGZIP(), []int{3} +} + +func (x *RegisterAgentResponse) GetHeartbeatIntervalSecond() int64 { + if x != nil { + return x.HeartbeatIntervalSecond + } + return 0 +} + +var File_homeops_hub_proto protoreflect.FileDescriptor + +const file_homeops_hub_proto_rawDesc = "" + + "\n" + + "\x11homeops/hub.proto\x1a\x1bgoogle/protobuf/empty.proto\"\"\n" + + "\fPongResponse\x12\x12\n" + + "\x04pong\x18\x01 \x01(\tR\x04pong\"\xc0\x01\n" + + "\x14RegisterAgentRequest\x12\x19\n" + + "\bagent_id\x18\x01 \x01(\tR\aagentId\x12\x1d\n" + + "\n" + + "agent_name\x18\x02 \x01(\tR\tagentName\x12\x1a\n" + + "\bhostname\x18\x03 \x01(\tR\bhostname\x12\x18\n" + + "\aversion\x18\x04 \x01(\tR\aversion\x12\x12\n" + + "\x04arch\x18\x05 \x01(\tR\x04arch\x12$\n" + + "\x06config\x18\x06 \x01(\v2\f.AgentConfigR\x06config\"=\n" + + "\vAgentConfig\x12\x16\n" + + "\x06system\x18\x01 \x01(\tR\x06system\x12\x16\n" + + "\x06docker\x18\x02 \x01(\bR\x06docker\"S\n" + + "\x15RegisterAgentResponse\x12:\n" + + "\x19heartbeat_interval_second\x18\x01 \x01(\x03R\x17heartbeatIntervalSecond2x\n" + + "\x03Hub\x12/\n" + + "\x04Ping\x12\x16.google.protobuf.Empty\x1a\r.PongResponse\"\x00\x12@\n" + + "\rRegisterAgent\x12\x15.RegisterAgentRequest\x1a\x16.RegisterAgentResponse\"\x00B AgentConfig + 4, // 1: Hub.Ping:input_type -> google.protobuf.Empty + 1, // 2: Hub.RegisterAgent:input_type -> RegisterAgentRequest + 0, // 3: Hub.Ping:output_type -> PongResponse + 3, // 4: Hub.RegisterAgent:output_type -> RegisterAgentResponse + 3, // [3:5] is the sub-list for method output_type + 1, // [1:3] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_homeops_hub_proto_init() } +func file_homeops_hub_proto_init() { + if File_homeops_hub_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_homeops_hub_proto_rawDesc), len(file_homeops_hub_proto_rawDesc)), + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_homeops_hub_proto_goTypes, + DependencyIndexes: file_homeops_hub_proto_depIdxs, + MessageInfos: file_homeops_hub_proto_msgTypes, + }.Build() + File_homeops_hub_proto = out.File + file_homeops_hub_proto_goTypes = nil + file_homeops_hub_proto_depIdxs = nil +} diff --git a/api/gen/homeops/hub_grpc.pb.go b/api/gen/homeops/hub_grpc.pb.go new file mode 100644 index 0000000..e032d6c --- /dev/null +++ b/api/gen/homeops/hub_grpc.pb.go @@ -0,0 +1,160 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.1 +// - protoc v7.34.1 +// source: homeops/hub.proto + +package homeops + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +// 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.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + Hub_Ping_FullMethodName = "/Hub/Ping" + Hub_RegisterAgent_FullMethodName = "/Hub/RegisterAgent" +) + +// HubClient is the client API for Hub 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 HubClient interface { + Ping(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*PongResponse, error) + RegisterAgent(ctx context.Context, in *RegisterAgentRequest, opts ...grpc.CallOption) (*RegisterAgentResponse, error) +} + +type hubClient struct { + cc grpc.ClientConnInterface +} + +func NewHubClient(cc grpc.ClientConnInterface) HubClient { + return &hubClient{cc} +} + +func (c *hubClient) Ping(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*PongResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PongResponse) + err := c.cc.Invoke(ctx, Hub_Ping_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *hubClient) RegisterAgent(ctx context.Context, in *RegisterAgentRequest, opts ...grpc.CallOption) (*RegisterAgentResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RegisterAgentResponse) + err := c.cc.Invoke(ctx, Hub_RegisterAgent_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// HubServer is the server API for Hub service. +// All implementations must embed UnimplementedHubServer +// for forward compatibility. +type HubServer interface { + Ping(context.Context, *emptypb.Empty) (*PongResponse, error) + RegisterAgent(context.Context, *RegisterAgentRequest) (*RegisterAgentResponse, error) + mustEmbedUnimplementedHubServer() +} + +// UnimplementedHubServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedHubServer struct{} + +func (UnimplementedHubServer) Ping(context.Context, *emptypb.Empty) (*PongResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Ping not implemented") +} +func (UnimplementedHubServer) RegisterAgent(context.Context, *RegisterAgentRequest) (*RegisterAgentResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RegisterAgent not implemented") +} +func (UnimplementedHubServer) mustEmbedUnimplementedHubServer() {} +func (UnimplementedHubServer) testEmbeddedByValue() {} + +// UnsafeHubServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to HubServer will +// result in compilation errors. +type UnsafeHubServer interface { + mustEmbedUnimplementedHubServer() +} + +func RegisterHubServer(s grpc.ServiceRegistrar, srv HubServer) { + // If the following call panics, it indicates UnimplementedHubServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&Hub_ServiceDesc, srv) +} + +func _Hub_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HubServer).Ping(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Hub_Ping_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HubServer).Ping(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Hub_RegisterAgent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RegisterAgentRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HubServer).RegisterAgent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Hub_RegisterAgent_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HubServer).RegisterAgent(ctx, req.(*RegisterAgentRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Hub_ServiceDesc is the grpc.ServiceDesc for Hub service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Hub_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "Hub", + HandlerType: (*HubServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Ping", + Handler: _Hub_Ping_Handler, + }, + { + MethodName: "RegisterAgent", + Handler: _Hub_RegisterAgent_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "homeops/hub.proto", +} diff --git a/api/proto/homeops/hub.proto b/api/proto/homeops/hub.proto new file mode 100644 index 0000000..01c9698 --- /dev/null +++ b/api/proto/homeops/hub.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; + +import "google/protobuf/empty.proto"; + +option go_package = "github.com/lorsanstand/HomeOps-Hub/api/gen/homeops;homeops"; + +service Hub { + rpc Ping (google.protobuf.Empty) returns (PongResponse) {} + rpc RegisterAgent (RegisterAgentRequest) returns (RegisterAgentResponse) {} +} + +message PongResponse { + string pong = 1; +} + +message RegisterAgentRequest { + string agent_id = 1; + string agent_name = 2; + string hostname = 3; + string version = 4; + string arch = 5; + AgentConfig config = 6; +} + +message AgentConfig { + string system = 1; + bool docker = 2; +} + +message RegisterAgentResponse { + int64 heartbeat_interval_second = 1; +} \ No newline at end of file diff --git a/cmd/agent/main.go b/cmd/agent/main.go index 06ab7d0..3a3986a 100644 --- a/cmd/agent/main.go +++ b/cmd/agent/main.go @@ -1 +1,8 @@ package main + +import "github.com/lorsanstand/HomeOps-Hub/internal/hub/app" + +func main() { + start := app.NewApp() + start.Run() +} diff --git a/cmd/hub/main.go b/cmd/hub/main.go index 06ab7d0..afc06ec 100644 --- a/cmd/hub/main.go +++ b/cmd/hub/main.go @@ -1 +1,9 @@ package main + +import "github.com/lorsanstand/HomeOps-Hub/internal/hub/app" + +func main() { + start := app.NewApp() + + start.Run() +} diff --git a/go.mod b/go.mod index 9b04953..c165978 100644 --- a/go.mod +++ b/go.mod @@ -2,14 +2,22 @@ module github.com/lorsanstand/HomeOps-Hub go 1.26.1 +require ( + github.com/ilyakaznacheev/cleanenv v1.5.0 + github.com/rs/zerolog v1.35.0 + google.golang.org/grpc v1.80.0 + google.golang.org/protobuf v1.36.11 +) + require ( github.com/BurntSushi/toml v1.2.1 // indirect - github.com/ilyakaznacheev/cleanenv v1.5.0 // indirect github.com/joho/godotenv v1.5.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/rs/zerolog v1.35.0 // indirect - golang.org/x/sys v0.29.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect ) diff --git a/go.sum b/go.sum index fc6972b..f0de907 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,17 @@ github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4= github.com/ilyakaznacheev/cleanenv v1.5.0/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= @@ -10,9 +22,34 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/rs/zerolog v1.35.0 h1:VD0ykx7HMiMJytqINBsKcbLS+BJ4WYjz+05us+LRTdI= github.com/rs/zerolog v1.35.0/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= +google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/agent/app/app.go b/internal/agent/app/app.go new file mode 100644 index 0000000..18f4f60 --- /dev/null +++ b/internal/agent/app/app.go @@ -0,0 +1,37 @@ +package app + +import ( + standartlog "log" + + "github.com/lorsanstand/HomeOps-Hub/internal/agent/rpc" + "github.com/lorsanstand/HomeOps-Hub/internal/agent/utils/config_yaml" + log2 "github.com/lorsanstand/HomeOps-Hub/internal/shared/log" + "github.com/rs/zerolog" +) + +type App struct { + log zerolog.Logger + cfg *config_yaml.AgentConfig + hubConn *rpc.Connection +} + +func NewApp() *App { + cfg, err := config_yaml.NewConfig() + if err != nil { + standartlog.Fatalf("failed get config: %v", err) + } + + log := log2.NewLogger(cfg) + + return &App{cfg: cfg, log: log} +} + +func (a *App) Run() { + conn, err := rpc.NewConnectAgent(a.cfg.GetGRPCAddress()) + if err != nil { + a.log.Error().Err(err) + return + } + + a.hubConn = conn +} diff --git a/internal/agent/rpc/client.go b/internal/agent/rpc/client.go new file mode 100644 index 0000000..174d0c4 --- /dev/null +++ b/internal/agent/rpc/client.go @@ -0,0 +1,32 @@ +package rpc + +import ( + "fmt" + + pb "github.com/lorsanstand/HomeOps-Hub/api/gen/homeops" + "google.golang.org/grpc" +) + +type Connection struct { + hub pb.HubClient + conn *grpc.ClientConn +} + +func NewConnectAgent(address string) (*Connection, error) { + conn, err := grpc.NewClient(address) + if err != nil { + return nil, fmt.Errorf("failed connection hub: %v", err) + } + + client := pb.NewHubClient(conn) + + return &Connection{hub: client, conn: conn}, nil +} + +func (c *Connection) Close() error { + return c.conn.Close() +} + +func (c *Connection) Hub() pb.HubClient { + return c.hub +} diff --git a/internal/agent/service/hub_service/hub.go b/internal/agent/service/hub_service/hub.go new file mode 100644 index 0000000..e80229d --- /dev/null +++ b/internal/agent/service/hub_service/hub.go @@ -0,0 +1 @@ +package hub_service diff --git a/internal/agent/utils/config_yaml/config.go b/internal/agent/utils/config_yaml/config.go index f9c2f72..5472b34 100644 --- a/internal/agent/utils/config_yaml/config.go +++ b/internal/agent/utils/config_yaml/config.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "github.com/rs/zerolog" "gopkg.in/yaml.v3" ) @@ -13,6 +14,7 @@ type AgentConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` } `yaml:"hub"` + LogLevel string `yaml:"log_level"` } func NewConfig() (*AgentConfig, error) { @@ -29,3 +31,19 @@ func NewConfig() (*AgentConfig, error) { return &cfg, nil } + +func (c *AgentConfig) GetLogLevel() zerolog.Level { + level, err := zerolog.ParseLevel(c.LogLevel) + if err != nil { + return zerolog.InfoLevel + } + return level +} + +func (c *AgentConfig) GetMode() string { + return "PROD" +} + +func (c *AgentConfig) GetGRPCAddress() string { + return fmt.Sprintf("%v:%v", c.HubConnect.Host, c.HubConnect.Port) +} diff --git a/internal/hub/app/app.go b/internal/hub/app/app.go new file mode 100644 index 0000000..0db6be1 --- /dev/null +++ b/internal/hub/app/app.go @@ -0,0 +1,55 @@ +package app + +import ( + "fmt" + standartlog "log" + "net" + + grpcserv "github.com/lorsanstand/HomeOps-Hub/internal/hub/rpc" + "github.com/lorsanstand/HomeOps-Hub/internal/shared/config" + "github.com/lorsanstand/HomeOps-Hub/internal/shared/log" + "github.com/rs/zerolog" +) + +type App struct { + cfg *config.Config + log zerolog.Logger + server *grpcserv.HubHandler +} + +func NewApp() *App { + cfg, err := config.NewConfig() + if err != nil { + standartlog.Fatalf("failed get config: %v", err) + } + + logger := log.NewLogger(cfg) + + server := grpcserv.NewHubHandler(logger) + + return &App{cfg: cfg, log: logger, server: server} +} + +func (a *App) Run() { + err := a.hubServe() + if err != nil { + a.log.Error().Err(err).Msg("failed start server") + } +} + +func (a *App) hubServe() error { + address := fmt.Sprintf("0.0.0.0:%v", a.cfg.Port) + a.log.Info().Str("address", "http://"+address).Msg("start GRPC server") + + lis, err := net.Listen("tcp", address) + if err != nil { + return err + } + + err = a.server.GrpcServer.Serve(lis) + if err != nil { + return err + } + + return nil +} diff --git a/internal/hub/rpc/server.go b/internal/hub/rpc/server.go new file mode 100644 index 0000000..0692d17 --- /dev/null +++ b/internal/hub/rpc/server.go @@ -0,0 +1,32 @@ +package rpc + +import ( + "context" + + pb "github.com/lorsanstand/HomeOps-Hub/api/gen/homeops" + "github.com/rs/zerolog" + "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/emptypb" +) + +type HubHandler struct { + pb.UnimplementedHubServer + log zerolog.Logger + GrpcServer *grpc.Server +} + +func NewHubHandler(logger zerolog.Logger) *HubHandler { + hub := &HubHandler{log: logger} + + grpcServer := grpc.NewServer() + pb.RegisterHubServer(grpcServer, hub) + + hub.GrpcServer = grpcServer + + return hub +} + +func (h *HubHandler) Ping(ctx context.Context, _ *emptypb.Empty) (*pb.PongResponse, error) { + h.log.Info().Msg("pong request") + return &pb.PongResponse{Pong: "Pong"}, nil +} diff --git a/internal/shared/cfg/config.go b/internal/shared/config/config.go similarity index 75% rename from internal/shared/cfg/config.go rename to internal/shared/config/config.go index dfd9754..5ce002d 100644 --- a/internal/shared/cfg/config.go +++ b/internal/shared/config/config.go @@ -1,4 +1,4 @@ -package cfg +package config import ( "fmt" @@ -9,13 +9,14 @@ import ( ) type Config struct { - DBHost string `env:"DB_HOST" env-required:"true"` - DBPort int `env:"DB_PORT" env-required:"true"` - DBPassword string `env:"DB_PASS" env-required:"true"` - DBUser string `env:"DB_USER" env-required:"true"` - DBName string `env:"DB_NAME" env-required:"true"` + DBHost string `env:"DB_HOST"` + DBPort int `env:"DB_PORT"` + DBPassword string `env:"DB_PASS"` + DBUser string `env:"DB_USER"` + DBName string `env:"DB_NAME"` LogLevel string `env:"LOG_LEVEL" env-default:"INFO"` Mode string `env:"MODE" env-default:"DEV"` + Port int `env:"PORT" env-default:"9000"` } func NewConfig() (*Config, error) { diff --git a/internal/shared/log/init.go b/internal/shared/log/init.go index f44fc6b..8e4661a 100644 --- a/internal/shared/log/init.go +++ b/internal/shared/log/init.go @@ -1,11 +1,11 @@ package log import ( + "io" "os" "time" "github.com/rs/zerolog" - "github.com/rs/zerolog/log" ) type cfgLogStore interface { @@ -13,15 +13,17 @@ type cfgLogStore interface { GetMode() string } -func Init(cfg cfgLogStore) { - zerolog.TimeFieldFormat = zerolog.TimeFormatUnix +func NewLogger(cfg cfgLogStore) zerolog.Logger { + var output io.Writer = os.Stdout if cfg.GetMode() != "PROD" { - log.Logger = log.Output(zerolog.ConsoleWriter{ + output = zerolog.ConsoleWriter{ Out: os.Stdout, TimeFormat: time.Kitchen, - }) - } else { - zerolog.SetGlobalLevel(zerolog.InfoLevel) + } } + + level := cfg.GetLogLevel() + + return zerolog.New(output).Level(level).With().Timestamp().Logger() }