From 569459209ab6f3def15ca80b964d53185b313a3c Mon Sep 17 00:00:00 2001 From: Stavros Kois Date: Wed, 25 Jun 2025 15:24:28 +0300 Subject: [PATCH] add health command --- cmd/headscale/cli/health.go | 39 ++ gen/go/headscale/v1/headscale.pb.go | 366 +++++++++++++----- gen/go/headscale/v1/headscale.pb.gw.go | 57 +++ gen/go/headscale/v1/headscale_grpc.pb.go | 40 ++ .../headscale/v1/headscale.swagger.json | 39 ++ hscontrol/grpcv1.go | 14 + proto/headscale/v1/headscale.proto | 19 + 7 files changed, 469 insertions(+), 105 deletions(-) create mode 100644 cmd/headscale/cli/health.go diff --git a/cmd/headscale/cli/health.go b/cmd/headscale/cli/health.go new file mode 100644 index 00000000..5535897d --- /dev/null +++ b/cmd/headscale/cli/health.go @@ -0,0 +1,39 @@ +package cli + +import ( + "fmt" + + v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(healthCmd) +} + +var healthCmd = &cobra.Command{ + Use: "health", + Short: "Check the health of the Headscale server", + Long: "Check the health of the Headscale server. This command will return an exit code of 0 if the server is healthy, or 1 if it is not.", + Run: func(cmd *cobra.Command, args []string) { + output, _ := cmd.Flags().GetString("output") + ctx, client, conn, cancel := newHeadscaleCLIWithConfig() + defer cancel() + defer conn.Close() + + response, err := client.Health(ctx, &v1.HealthRequest{}) + if err != nil { + ErrorOutput(err, "Error checking health", output) + } + + if response.Status == v1.HealthStatus_Pass { + SuccessOutput("Headscale server is healthy", "", output) + } + + ErrorOutput( + fmt.Errorf("server health check failed: %s", response.Status), + "Headscale server is unhealthy", + output, + ) + }, +} diff --git a/gen/go/headscale/v1/headscale.pb.go b/gen/go/headscale/v1/headscale.pb.go index aa3380c6..a0ed29c6 100644 --- a/gen/go/headscale/v1/headscale.pb.go +++ b/gen/go/headscale/v1/headscale.pb.go @@ -11,6 +11,7 @@ import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" + sync "sync" unsafe "unsafe" ) @@ -21,11 +22,143 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type HealthStatus int32 + +const ( + HealthStatus_Fail HealthStatus = 0 + HealthStatus_Pass HealthStatus = 1 +) + +// Enum value maps for HealthStatus. +var ( + HealthStatus_name = map[int32]string{ + 0: "Fail", + 1: "Pass", + } + HealthStatus_value = map[string]int32{ + "Fail": 0, + "Pass": 1, + } +) + +func (x HealthStatus) Enum() *HealthStatus { + p := new(HealthStatus) + *p = x + return p +} + +func (x HealthStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (HealthStatus) Descriptor() protoreflect.EnumDescriptor { + return file_headscale_v1_headscale_proto_enumTypes[0].Descriptor() +} + +func (HealthStatus) Type() protoreflect.EnumType { + return &file_headscale_v1_headscale_proto_enumTypes[0] +} + +func (x HealthStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use HealthStatus.Descriptor instead. +func (HealthStatus) EnumDescriptor() ([]byte, []int) { + return file_headscale_v1_headscale_proto_rawDescGZIP(), []int{0} +} + +type HealthRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HealthRequest) Reset() { + *x = HealthRequest{} + mi := &file_headscale_v1_headscale_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HealthRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HealthRequest) ProtoMessage() {} + +func (x *HealthRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_headscale_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 HealthRequest.ProtoReflect.Descriptor instead. +func (*HealthRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_headscale_proto_rawDescGZIP(), []int{0} +} + +type HealthResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status HealthStatus `protobuf:"varint,1,opt,name=status,proto3,enum=headscale.v1.HealthStatus" json:"status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HealthResponse) Reset() { + *x = HealthResponse{} + mi := &file_headscale_v1_headscale_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HealthResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HealthResponse) ProtoMessage() {} + +func (x *HealthResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_headscale_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 HealthResponse.ProtoReflect.Descriptor instead. +func (*HealthResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_headscale_proto_rawDescGZIP(), []int{1} +} + +func (x *HealthResponse) GetStatus() HealthStatus { + if x != nil { + return x.Status + } + return HealthStatus_Fail +} + var File_headscale_v1_headscale_proto protoreflect.FileDescriptor const file_headscale_v1_headscale_proto_rawDesc = "" + "\n" + - "\x1cheadscale/v1/headscale.proto\x12\fheadscale.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x17headscale/v1/user.proto\x1a\x1dheadscale/v1/preauthkey.proto\x1a\x17headscale/v1/node.proto\x1a\x19headscale/v1/apikey.proto\x1a\x19headscale/v1/policy.proto2\xa3\x16\n" + + "\x1cheadscale/v1/headscale.proto\x12\fheadscale.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x17headscale/v1/user.proto\x1a\x1dheadscale/v1/preauthkey.proto\x1a\x17headscale/v1/node.proto\x1a\x19headscale/v1/apikey.proto\x1a\x19headscale/v1/policy.proto\"\x0f\n" + + "\rHealthRequest\"D\n" + + "\x0eHealthResponse\x122\n" + + "\x06status\x18\x01 \x01(\x0e2\x1a.headscale.v1.HealthStatusR\x06status*\"\n" + + "\fHealthStatus\x12\b\n" + + "\x04Fail\x10\x00\x12\b\n" + + "\x04Pass\x10\x012\x80\x17\n" + "\x10HeadscaleService\x12h\n" + "\n" + "CreateUser\x12\x1f.headscale.v1.CreateUserRequest\x1a .headscale.v1.CreateUserResponse\"\x17\x82\xd3\xe4\x93\x02\x11:\x01*\"\f/api/v1/user\x12\x80\x01\n" + @@ -56,112 +189,133 @@ const file_headscale_v1_headscale_proto_rawDesc = "" + "\vListApiKeys\x12 .headscale.v1.ListApiKeysRequest\x1a!.headscale.v1.ListApiKeysResponse\"\x16\x82\xd3\xe4\x93\x02\x10\x12\x0e/api/v1/apikey\x12v\n" + "\fDeleteApiKey\x12!.headscale.v1.DeleteApiKeyRequest\x1a\".headscale.v1.DeleteApiKeyResponse\"\x1f\x82\xd3\xe4\x93\x02\x19*\x17/api/v1/apikey/{prefix}\x12d\n" + "\tGetPolicy\x12\x1e.headscale.v1.GetPolicyRequest\x1a\x1f.headscale.v1.GetPolicyResponse\"\x16\x82\xd3\xe4\x93\x02\x10\x12\x0e/api/v1/policy\x12g\n" + - "\tSetPolicy\x12\x1e.headscale.v1.SetPolicyRequest\x1a\x1f.headscale.v1.SetPolicyResponse\"\x19\x82\xd3\xe4\x93\x02\x13:\x01*\x1a\x0e/api/v1/policyB)Z'github.com/juanfont/headscale/gen/go/v1b\x06proto3" + "\tSetPolicy\x12\x1e.headscale.v1.SetPolicyRequest\x1a\x1f.headscale.v1.SetPolicyResponse\"\x19\x82\xd3\xe4\x93\x02\x13:\x01*\x1a\x0e/api/v1/policy\x12[\n" + + "\x06Health\x12\x1b.headscale.v1.HealthRequest\x1a\x1c.headscale.v1.HealthResponse\"\x16\x82\xd3\xe4\x93\x02\x10\x12\x0e/api/v1/healthB)Z'github.com/juanfont/headscale/gen/go/v1b\x06proto3" +var ( + file_headscale_v1_headscale_proto_rawDescOnce sync.Once + file_headscale_v1_headscale_proto_rawDescData []byte +) + +func file_headscale_v1_headscale_proto_rawDescGZIP() []byte { + file_headscale_v1_headscale_proto_rawDescOnce.Do(func() { + file_headscale_v1_headscale_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_headscale_v1_headscale_proto_rawDesc), len(file_headscale_v1_headscale_proto_rawDesc))) + }) + return file_headscale_v1_headscale_proto_rawDescData +} + +var file_headscale_v1_headscale_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_headscale_v1_headscale_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_headscale_v1_headscale_proto_goTypes = []any{ - (*CreateUserRequest)(nil), // 0: headscale.v1.CreateUserRequest - (*RenameUserRequest)(nil), // 1: headscale.v1.RenameUserRequest - (*DeleteUserRequest)(nil), // 2: headscale.v1.DeleteUserRequest - (*ListUsersRequest)(nil), // 3: headscale.v1.ListUsersRequest - (*CreatePreAuthKeyRequest)(nil), // 4: headscale.v1.CreatePreAuthKeyRequest - (*ExpirePreAuthKeyRequest)(nil), // 5: headscale.v1.ExpirePreAuthKeyRequest - (*ListPreAuthKeysRequest)(nil), // 6: headscale.v1.ListPreAuthKeysRequest - (*DebugCreateNodeRequest)(nil), // 7: headscale.v1.DebugCreateNodeRequest - (*GetNodeRequest)(nil), // 8: headscale.v1.GetNodeRequest - (*SetTagsRequest)(nil), // 9: headscale.v1.SetTagsRequest - (*SetApprovedRoutesRequest)(nil), // 10: headscale.v1.SetApprovedRoutesRequest - (*RegisterNodeRequest)(nil), // 11: headscale.v1.RegisterNodeRequest - (*DeleteNodeRequest)(nil), // 12: headscale.v1.DeleteNodeRequest - (*ExpireNodeRequest)(nil), // 13: headscale.v1.ExpireNodeRequest - (*RenameNodeRequest)(nil), // 14: headscale.v1.RenameNodeRequest - (*ListNodesRequest)(nil), // 15: headscale.v1.ListNodesRequest - (*MoveNodeRequest)(nil), // 16: headscale.v1.MoveNodeRequest - (*BackfillNodeIPsRequest)(nil), // 17: headscale.v1.BackfillNodeIPsRequest - (*CreateApiKeyRequest)(nil), // 18: headscale.v1.CreateApiKeyRequest - (*ExpireApiKeyRequest)(nil), // 19: headscale.v1.ExpireApiKeyRequest - (*ListApiKeysRequest)(nil), // 20: headscale.v1.ListApiKeysRequest - (*DeleteApiKeyRequest)(nil), // 21: headscale.v1.DeleteApiKeyRequest - (*GetPolicyRequest)(nil), // 22: headscale.v1.GetPolicyRequest - (*SetPolicyRequest)(nil), // 23: headscale.v1.SetPolicyRequest - (*CreateUserResponse)(nil), // 24: headscale.v1.CreateUserResponse - (*RenameUserResponse)(nil), // 25: headscale.v1.RenameUserResponse - (*DeleteUserResponse)(nil), // 26: headscale.v1.DeleteUserResponse - (*ListUsersResponse)(nil), // 27: headscale.v1.ListUsersResponse - (*CreatePreAuthKeyResponse)(nil), // 28: headscale.v1.CreatePreAuthKeyResponse - (*ExpirePreAuthKeyResponse)(nil), // 29: headscale.v1.ExpirePreAuthKeyResponse - (*ListPreAuthKeysResponse)(nil), // 30: headscale.v1.ListPreAuthKeysResponse - (*DebugCreateNodeResponse)(nil), // 31: headscale.v1.DebugCreateNodeResponse - (*GetNodeResponse)(nil), // 32: headscale.v1.GetNodeResponse - (*SetTagsResponse)(nil), // 33: headscale.v1.SetTagsResponse - (*SetApprovedRoutesResponse)(nil), // 34: headscale.v1.SetApprovedRoutesResponse - (*RegisterNodeResponse)(nil), // 35: headscale.v1.RegisterNodeResponse - (*DeleteNodeResponse)(nil), // 36: headscale.v1.DeleteNodeResponse - (*ExpireNodeResponse)(nil), // 37: headscale.v1.ExpireNodeResponse - (*RenameNodeResponse)(nil), // 38: headscale.v1.RenameNodeResponse - (*ListNodesResponse)(nil), // 39: headscale.v1.ListNodesResponse - (*MoveNodeResponse)(nil), // 40: headscale.v1.MoveNodeResponse - (*BackfillNodeIPsResponse)(nil), // 41: headscale.v1.BackfillNodeIPsResponse - (*CreateApiKeyResponse)(nil), // 42: headscale.v1.CreateApiKeyResponse - (*ExpireApiKeyResponse)(nil), // 43: headscale.v1.ExpireApiKeyResponse - (*ListApiKeysResponse)(nil), // 44: headscale.v1.ListApiKeysResponse - (*DeleteApiKeyResponse)(nil), // 45: headscale.v1.DeleteApiKeyResponse - (*GetPolicyResponse)(nil), // 46: headscale.v1.GetPolicyResponse - (*SetPolicyResponse)(nil), // 47: headscale.v1.SetPolicyResponse + (HealthStatus)(0), // 0: headscale.v1.HealthStatus + (*HealthRequest)(nil), // 1: headscale.v1.HealthRequest + (*HealthResponse)(nil), // 2: headscale.v1.HealthResponse + (*CreateUserRequest)(nil), // 3: headscale.v1.CreateUserRequest + (*RenameUserRequest)(nil), // 4: headscale.v1.RenameUserRequest + (*DeleteUserRequest)(nil), // 5: headscale.v1.DeleteUserRequest + (*ListUsersRequest)(nil), // 6: headscale.v1.ListUsersRequest + (*CreatePreAuthKeyRequest)(nil), // 7: headscale.v1.CreatePreAuthKeyRequest + (*ExpirePreAuthKeyRequest)(nil), // 8: headscale.v1.ExpirePreAuthKeyRequest + (*ListPreAuthKeysRequest)(nil), // 9: headscale.v1.ListPreAuthKeysRequest + (*DebugCreateNodeRequest)(nil), // 10: headscale.v1.DebugCreateNodeRequest + (*GetNodeRequest)(nil), // 11: headscale.v1.GetNodeRequest + (*SetTagsRequest)(nil), // 12: headscale.v1.SetTagsRequest + (*SetApprovedRoutesRequest)(nil), // 13: headscale.v1.SetApprovedRoutesRequest + (*RegisterNodeRequest)(nil), // 14: headscale.v1.RegisterNodeRequest + (*DeleteNodeRequest)(nil), // 15: headscale.v1.DeleteNodeRequest + (*ExpireNodeRequest)(nil), // 16: headscale.v1.ExpireNodeRequest + (*RenameNodeRequest)(nil), // 17: headscale.v1.RenameNodeRequest + (*ListNodesRequest)(nil), // 18: headscale.v1.ListNodesRequest + (*MoveNodeRequest)(nil), // 19: headscale.v1.MoveNodeRequest + (*BackfillNodeIPsRequest)(nil), // 20: headscale.v1.BackfillNodeIPsRequest + (*CreateApiKeyRequest)(nil), // 21: headscale.v1.CreateApiKeyRequest + (*ExpireApiKeyRequest)(nil), // 22: headscale.v1.ExpireApiKeyRequest + (*ListApiKeysRequest)(nil), // 23: headscale.v1.ListApiKeysRequest + (*DeleteApiKeyRequest)(nil), // 24: headscale.v1.DeleteApiKeyRequest + (*GetPolicyRequest)(nil), // 25: headscale.v1.GetPolicyRequest + (*SetPolicyRequest)(nil), // 26: headscale.v1.SetPolicyRequest + (*CreateUserResponse)(nil), // 27: headscale.v1.CreateUserResponse + (*RenameUserResponse)(nil), // 28: headscale.v1.RenameUserResponse + (*DeleteUserResponse)(nil), // 29: headscale.v1.DeleteUserResponse + (*ListUsersResponse)(nil), // 30: headscale.v1.ListUsersResponse + (*CreatePreAuthKeyResponse)(nil), // 31: headscale.v1.CreatePreAuthKeyResponse + (*ExpirePreAuthKeyResponse)(nil), // 32: headscale.v1.ExpirePreAuthKeyResponse + (*ListPreAuthKeysResponse)(nil), // 33: headscale.v1.ListPreAuthKeysResponse + (*DebugCreateNodeResponse)(nil), // 34: headscale.v1.DebugCreateNodeResponse + (*GetNodeResponse)(nil), // 35: headscale.v1.GetNodeResponse + (*SetTagsResponse)(nil), // 36: headscale.v1.SetTagsResponse + (*SetApprovedRoutesResponse)(nil), // 37: headscale.v1.SetApprovedRoutesResponse + (*RegisterNodeResponse)(nil), // 38: headscale.v1.RegisterNodeResponse + (*DeleteNodeResponse)(nil), // 39: headscale.v1.DeleteNodeResponse + (*ExpireNodeResponse)(nil), // 40: headscale.v1.ExpireNodeResponse + (*RenameNodeResponse)(nil), // 41: headscale.v1.RenameNodeResponse + (*ListNodesResponse)(nil), // 42: headscale.v1.ListNodesResponse + (*MoveNodeResponse)(nil), // 43: headscale.v1.MoveNodeResponse + (*BackfillNodeIPsResponse)(nil), // 44: headscale.v1.BackfillNodeIPsResponse + (*CreateApiKeyResponse)(nil), // 45: headscale.v1.CreateApiKeyResponse + (*ExpireApiKeyResponse)(nil), // 46: headscale.v1.ExpireApiKeyResponse + (*ListApiKeysResponse)(nil), // 47: headscale.v1.ListApiKeysResponse + (*DeleteApiKeyResponse)(nil), // 48: headscale.v1.DeleteApiKeyResponse + (*GetPolicyResponse)(nil), // 49: headscale.v1.GetPolicyResponse + (*SetPolicyResponse)(nil), // 50: headscale.v1.SetPolicyResponse } var file_headscale_v1_headscale_proto_depIdxs = []int32{ - 0, // 0: headscale.v1.HeadscaleService.CreateUser:input_type -> headscale.v1.CreateUserRequest - 1, // 1: headscale.v1.HeadscaleService.RenameUser:input_type -> headscale.v1.RenameUserRequest - 2, // 2: headscale.v1.HeadscaleService.DeleteUser:input_type -> headscale.v1.DeleteUserRequest - 3, // 3: headscale.v1.HeadscaleService.ListUsers:input_type -> headscale.v1.ListUsersRequest - 4, // 4: headscale.v1.HeadscaleService.CreatePreAuthKey:input_type -> headscale.v1.CreatePreAuthKeyRequest - 5, // 5: headscale.v1.HeadscaleService.ExpirePreAuthKey:input_type -> headscale.v1.ExpirePreAuthKeyRequest - 6, // 6: headscale.v1.HeadscaleService.ListPreAuthKeys:input_type -> headscale.v1.ListPreAuthKeysRequest - 7, // 7: headscale.v1.HeadscaleService.DebugCreateNode:input_type -> headscale.v1.DebugCreateNodeRequest - 8, // 8: headscale.v1.HeadscaleService.GetNode:input_type -> headscale.v1.GetNodeRequest - 9, // 9: headscale.v1.HeadscaleService.SetTags:input_type -> headscale.v1.SetTagsRequest - 10, // 10: headscale.v1.HeadscaleService.SetApprovedRoutes:input_type -> headscale.v1.SetApprovedRoutesRequest - 11, // 11: headscale.v1.HeadscaleService.RegisterNode:input_type -> headscale.v1.RegisterNodeRequest - 12, // 12: headscale.v1.HeadscaleService.DeleteNode:input_type -> headscale.v1.DeleteNodeRequest - 13, // 13: headscale.v1.HeadscaleService.ExpireNode:input_type -> headscale.v1.ExpireNodeRequest - 14, // 14: headscale.v1.HeadscaleService.RenameNode:input_type -> headscale.v1.RenameNodeRequest - 15, // 15: headscale.v1.HeadscaleService.ListNodes:input_type -> headscale.v1.ListNodesRequest - 16, // 16: headscale.v1.HeadscaleService.MoveNode:input_type -> headscale.v1.MoveNodeRequest - 17, // 17: headscale.v1.HeadscaleService.BackfillNodeIPs:input_type -> headscale.v1.BackfillNodeIPsRequest - 18, // 18: headscale.v1.HeadscaleService.CreateApiKey:input_type -> headscale.v1.CreateApiKeyRequest - 19, // 19: headscale.v1.HeadscaleService.ExpireApiKey:input_type -> headscale.v1.ExpireApiKeyRequest - 20, // 20: headscale.v1.HeadscaleService.ListApiKeys:input_type -> headscale.v1.ListApiKeysRequest - 21, // 21: headscale.v1.HeadscaleService.DeleteApiKey:input_type -> headscale.v1.DeleteApiKeyRequest - 22, // 22: headscale.v1.HeadscaleService.GetPolicy:input_type -> headscale.v1.GetPolicyRequest - 23, // 23: headscale.v1.HeadscaleService.SetPolicy:input_type -> headscale.v1.SetPolicyRequest - 24, // 24: headscale.v1.HeadscaleService.CreateUser:output_type -> headscale.v1.CreateUserResponse - 25, // 25: headscale.v1.HeadscaleService.RenameUser:output_type -> headscale.v1.RenameUserResponse - 26, // 26: headscale.v1.HeadscaleService.DeleteUser:output_type -> headscale.v1.DeleteUserResponse - 27, // 27: headscale.v1.HeadscaleService.ListUsers:output_type -> headscale.v1.ListUsersResponse - 28, // 28: headscale.v1.HeadscaleService.CreatePreAuthKey:output_type -> headscale.v1.CreatePreAuthKeyResponse - 29, // 29: headscale.v1.HeadscaleService.ExpirePreAuthKey:output_type -> headscale.v1.ExpirePreAuthKeyResponse - 30, // 30: headscale.v1.HeadscaleService.ListPreAuthKeys:output_type -> headscale.v1.ListPreAuthKeysResponse - 31, // 31: headscale.v1.HeadscaleService.DebugCreateNode:output_type -> headscale.v1.DebugCreateNodeResponse - 32, // 32: headscale.v1.HeadscaleService.GetNode:output_type -> headscale.v1.GetNodeResponse - 33, // 33: headscale.v1.HeadscaleService.SetTags:output_type -> headscale.v1.SetTagsResponse - 34, // 34: headscale.v1.HeadscaleService.SetApprovedRoutes:output_type -> headscale.v1.SetApprovedRoutesResponse - 35, // 35: headscale.v1.HeadscaleService.RegisterNode:output_type -> headscale.v1.RegisterNodeResponse - 36, // 36: headscale.v1.HeadscaleService.DeleteNode:output_type -> headscale.v1.DeleteNodeResponse - 37, // 37: headscale.v1.HeadscaleService.ExpireNode:output_type -> headscale.v1.ExpireNodeResponse - 38, // 38: headscale.v1.HeadscaleService.RenameNode:output_type -> headscale.v1.RenameNodeResponse - 39, // 39: headscale.v1.HeadscaleService.ListNodes:output_type -> headscale.v1.ListNodesResponse - 40, // 40: headscale.v1.HeadscaleService.MoveNode:output_type -> headscale.v1.MoveNodeResponse - 41, // 41: headscale.v1.HeadscaleService.BackfillNodeIPs:output_type -> headscale.v1.BackfillNodeIPsResponse - 42, // 42: headscale.v1.HeadscaleService.CreateApiKey:output_type -> headscale.v1.CreateApiKeyResponse - 43, // 43: headscale.v1.HeadscaleService.ExpireApiKey:output_type -> headscale.v1.ExpireApiKeyResponse - 44, // 44: headscale.v1.HeadscaleService.ListApiKeys:output_type -> headscale.v1.ListApiKeysResponse - 45, // 45: headscale.v1.HeadscaleService.DeleteApiKey:output_type -> headscale.v1.DeleteApiKeyResponse - 46, // 46: headscale.v1.HeadscaleService.GetPolicy:output_type -> headscale.v1.GetPolicyResponse - 47, // 47: headscale.v1.HeadscaleService.SetPolicy:output_type -> headscale.v1.SetPolicyResponse - 24, // [24:48] is the sub-list for method output_type - 0, // [0:24] 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 + 0, // 0: headscale.v1.HealthResponse.status:type_name -> headscale.v1.HealthStatus + 3, // 1: headscale.v1.HeadscaleService.CreateUser:input_type -> headscale.v1.CreateUserRequest + 4, // 2: headscale.v1.HeadscaleService.RenameUser:input_type -> headscale.v1.RenameUserRequest + 5, // 3: headscale.v1.HeadscaleService.DeleteUser:input_type -> headscale.v1.DeleteUserRequest + 6, // 4: headscale.v1.HeadscaleService.ListUsers:input_type -> headscale.v1.ListUsersRequest + 7, // 5: headscale.v1.HeadscaleService.CreatePreAuthKey:input_type -> headscale.v1.CreatePreAuthKeyRequest + 8, // 6: headscale.v1.HeadscaleService.ExpirePreAuthKey:input_type -> headscale.v1.ExpirePreAuthKeyRequest + 9, // 7: headscale.v1.HeadscaleService.ListPreAuthKeys:input_type -> headscale.v1.ListPreAuthKeysRequest + 10, // 8: headscale.v1.HeadscaleService.DebugCreateNode:input_type -> headscale.v1.DebugCreateNodeRequest + 11, // 9: headscale.v1.HeadscaleService.GetNode:input_type -> headscale.v1.GetNodeRequest + 12, // 10: headscale.v1.HeadscaleService.SetTags:input_type -> headscale.v1.SetTagsRequest + 13, // 11: headscale.v1.HeadscaleService.SetApprovedRoutes:input_type -> headscale.v1.SetApprovedRoutesRequest + 14, // 12: headscale.v1.HeadscaleService.RegisterNode:input_type -> headscale.v1.RegisterNodeRequest + 15, // 13: headscale.v1.HeadscaleService.DeleteNode:input_type -> headscale.v1.DeleteNodeRequest + 16, // 14: headscale.v1.HeadscaleService.ExpireNode:input_type -> headscale.v1.ExpireNodeRequest + 17, // 15: headscale.v1.HeadscaleService.RenameNode:input_type -> headscale.v1.RenameNodeRequest + 18, // 16: headscale.v1.HeadscaleService.ListNodes:input_type -> headscale.v1.ListNodesRequest + 19, // 17: headscale.v1.HeadscaleService.MoveNode:input_type -> headscale.v1.MoveNodeRequest + 20, // 18: headscale.v1.HeadscaleService.BackfillNodeIPs:input_type -> headscale.v1.BackfillNodeIPsRequest + 21, // 19: headscale.v1.HeadscaleService.CreateApiKey:input_type -> headscale.v1.CreateApiKeyRequest + 22, // 20: headscale.v1.HeadscaleService.ExpireApiKey:input_type -> headscale.v1.ExpireApiKeyRequest + 23, // 21: headscale.v1.HeadscaleService.ListApiKeys:input_type -> headscale.v1.ListApiKeysRequest + 24, // 22: headscale.v1.HeadscaleService.DeleteApiKey:input_type -> headscale.v1.DeleteApiKeyRequest + 25, // 23: headscale.v1.HeadscaleService.GetPolicy:input_type -> headscale.v1.GetPolicyRequest + 26, // 24: headscale.v1.HeadscaleService.SetPolicy:input_type -> headscale.v1.SetPolicyRequest + 1, // 25: headscale.v1.HeadscaleService.Health:input_type -> headscale.v1.HealthRequest + 27, // 26: headscale.v1.HeadscaleService.CreateUser:output_type -> headscale.v1.CreateUserResponse + 28, // 27: headscale.v1.HeadscaleService.RenameUser:output_type -> headscale.v1.RenameUserResponse + 29, // 28: headscale.v1.HeadscaleService.DeleteUser:output_type -> headscale.v1.DeleteUserResponse + 30, // 29: headscale.v1.HeadscaleService.ListUsers:output_type -> headscale.v1.ListUsersResponse + 31, // 30: headscale.v1.HeadscaleService.CreatePreAuthKey:output_type -> headscale.v1.CreatePreAuthKeyResponse + 32, // 31: headscale.v1.HeadscaleService.ExpirePreAuthKey:output_type -> headscale.v1.ExpirePreAuthKeyResponse + 33, // 32: headscale.v1.HeadscaleService.ListPreAuthKeys:output_type -> headscale.v1.ListPreAuthKeysResponse + 34, // 33: headscale.v1.HeadscaleService.DebugCreateNode:output_type -> headscale.v1.DebugCreateNodeResponse + 35, // 34: headscale.v1.HeadscaleService.GetNode:output_type -> headscale.v1.GetNodeResponse + 36, // 35: headscale.v1.HeadscaleService.SetTags:output_type -> headscale.v1.SetTagsResponse + 37, // 36: headscale.v1.HeadscaleService.SetApprovedRoutes:output_type -> headscale.v1.SetApprovedRoutesResponse + 38, // 37: headscale.v1.HeadscaleService.RegisterNode:output_type -> headscale.v1.RegisterNodeResponse + 39, // 38: headscale.v1.HeadscaleService.DeleteNode:output_type -> headscale.v1.DeleteNodeResponse + 40, // 39: headscale.v1.HeadscaleService.ExpireNode:output_type -> headscale.v1.ExpireNodeResponse + 41, // 40: headscale.v1.HeadscaleService.RenameNode:output_type -> headscale.v1.RenameNodeResponse + 42, // 41: headscale.v1.HeadscaleService.ListNodes:output_type -> headscale.v1.ListNodesResponse + 43, // 42: headscale.v1.HeadscaleService.MoveNode:output_type -> headscale.v1.MoveNodeResponse + 44, // 43: headscale.v1.HeadscaleService.BackfillNodeIPs:output_type -> headscale.v1.BackfillNodeIPsResponse + 45, // 44: headscale.v1.HeadscaleService.CreateApiKey:output_type -> headscale.v1.CreateApiKeyResponse + 46, // 45: headscale.v1.HeadscaleService.ExpireApiKey:output_type -> headscale.v1.ExpireApiKeyResponse + 47, // 46: headscale.v1.HeadscaleService.ListApiKeys:output_type -> headscale.v1.ListApiKeysResponse + 48, // 47: headscale.v1.HeadscaleService.DeleteApiKey:output_type -> headscale.v1.DeleteApiKeyResponse + 49, // 48: headscale.v1.HeadscaleService.GetPolicy:output_type -> headscale.v1.GetPolicyResponse + 50, // 49: headscale.v1.HeadscaleService.SetPolicy:output_type -> headscale.v1.SetPolicyResponse + 2, // 50: headscale.v1.HeadscaleService.Health:output_type -> headscale.v1.HealthResponse + 26, // [26:51] is the sub-list for method output_type + 1, // [1:26] 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_headscale_v1_headscale_proto_init() } @@ -179,13 +333,15 @@ func file_headscale_v1_headscale_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_headscale_v1_headscale_proto_rawDesc), len(file_headscale_v1_headscale_proto_rawDesc)), - NumEnums: 0, - NumMessages: 0, + NumEnums: 1, + NumMessages: 2, NumExtensions: 0, NumServices: 1, }, GoTypes: file_headscale_v1_headscale_proto_goTypes, DependencyIndexes: file_headscale_v1_headscale_proto_depIdxs, + EnumInfos: file_headscale_v1_headscale_proto_enumTypes, + MessageInfos: file_headscale_v1_headscale_proto_msgTypes, }.Build() File_headscale_v1_headscale_proto = out.File file_headscale_v1_headscale_proto_goTypes = nil diff --git a/gen/go/headscale/v1/headscale.pb.gw.go b/gen/go/headscale/v1/headscale.pb.gw.go index 2e1cc480..fcd7fa2b 100644 --- a/gen/go/headscale/v1/headscale.pb.gw.go +++ b/gen/go/headscale/v1/headscale.pb.gw.go @@ -809,6 +809,24 @@ func local_request_HeadscaleService_SetPolicy_0(ctx context.Context, marshaler r return msg, metadata, err } +func request_HeadscaleService_Health_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq HealthRequest + metadata runtime.ServerMetadata + ) + msg, err := client.Health(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_Health_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq HealthRequest + metadata runtime.ServerMetadata + ) + msg, err := server.Health(ctx, &protoReq) + return msg, metadata, err +} + // RegisterHeadscaleServiceHandlerServer registers the http handlers for service HeadscaleService to "mux". // UnaryRPC :call HeadscaleServiceServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -1295,6 +1313,26 @@ func RegisterHeadscaleServiceHandlerServer(ctx context.Context, mux *runtime.Ser } forward_HeadscaleService_SetPolicy_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) + mux.Handle(http.MethodGet, pattern_HeadscaleService_Health_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/Health", runtime.WithHTTPPathPattern("/api/v1/health")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_Health_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_Health_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) return nil } @@ -1743,6 +1781,23 @@ func RegisterHeadscaleServiceHandlerClient(ctx context.Context, mux *runtime.Ser } forward_HeadscaleService_SetPolicy_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) + mux.Handle(http.MethodGet, pattern_HeadscaleService_Health_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/Health", runtime.WithHTTPPathPattern("/api/v1/health")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_Health_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_Health_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) return nil } @@ -1771,6 +1826,7 @@ var ( pattern_HeadscaleService_DeleteApiKey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v1", "apikey", "prefix"}, "")) pattern_HeadscaleService_GetPolicy_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "policy"}, "")) pattern_HeadscaleService_SetPolicy_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "policy"}, "")) + pattern_HeadscaleService_Health_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "health"}, "")) ) var ( @@ -1798,4 +1854,5 @@ var ( forward_HeadscaleService_DeleteApiKey_0 = runtime.ForwardResponseMessage forward_HeadscaleService_GetPolicy_0 = runtime.ForwardResponseMessage forward_HeadscaleService_SetPolicy_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_Health_0 = runtime.ForwardResponseMessage ) diff --git a/gen/go/headscale/v1/headscale_grpc.pb.go b/gen/go/headscale/v1/headscale_grpc.pb.go index f6d6687a..bd8428c2 100644 --- a/gen/go/headscale/v1/headscale_grpc.pb.go +++ b/gen/go/headscale/v1/headscale_grpc.pb.go @@ -43,6 +43,7 @@ const ( HeadscaleService_DeleteApiKey_FullMethodName = "/headscale.v1.HeadscaleService/DeleteApiKey" HeadscaleService_GetPolicy_FullMethodName = "/headscale.v1.HeadscaleService/GetPolicy" HeadscaleService_SetPolicy_FullMethodName = "/headscale.v1.HeadscaleService/SetPolicy" + HeadscaleService_Health_FullMethodName = "/headscale.v1.HeadscaleService/Health" ) // HeadscaleServiceClient is the client API for HeadscaleService service. @@ -78,6 +79,8 @@ type HeadscaleServiceClient interface { // --- Policy start --- GetPolicy(ctx context.Context, in *GetPolicyRequest, opts ...grpc.CallOption) (*GetPolicyResponse, error) SetPolicy(ctx context.Context, in *SetPolicyRequest, opts ...grpc.CallOption) (*SetPolicyResponse, error) + // --- Health start --- + Health(ctx context.Context, in *HealthRequest, opts ...grpc.CallOption) (*HealthResponse, error) } type headscaleServiceClient struct { @@ -328,6 +331,16 @@ func (c *headscaleServiceClient) SetPolicy(ctx context.Context, in *SetPolicyReq return out, nil } +func (c *headscaleServiceClient) Health(ctx context.Context, in *HealthRequest, opts ...grpc.CallOption) (*HealthResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(HealthResponse) + err := c.cc.Invoke(ctx, HeadscaleService_Health_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // HeadscaleServiceServer is the server API for HeadscaleService service. // All implementations must embed UnimplementedHeadscaleServiceServer // for forward compatibility. @@ -361,6 +374,8 @@ type HeadscaleServiceServer interface { // --- Policy start --- GetPolicy(context.Context, *GetPolicyRequest) (*GetPolicyResponse, error) SetPolicy(context.Context, *SetPolicyRequest) (*SetPolicyResponse, error) + // --- Health start --- + Health(context.Context, *HealthRequest) (*HealthResponse, error) mustEmbedUnimplementedHeadscaleServiceServer() } @@ -443,6 +458,9 @@ func (UnimplementedHeadscaleServiceServer) GetPolicy(context.Context, *GetPolicy func (UnimplementedHeadscaleServiceServer) SetPolicy(context.Context, *SetPolicyRequest) (*SetPolicyResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SetPolicy not implemented") } +func (UnimplementedHeadscaleServiceServer) Health(context.Context, *HealthRequest) (*HealthResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Health not implemented") +} func (UnimplementedHeadscaleServiceServer) mustEmbedUnimplementedHeadscaleServiceServer() {} func (UnimplementedHeadscaleServiceServer) testEmbeddedByValue() {} @@ -896,6 +914,24 @@ func _HeadscaleService_SetPolicy_Handler(srv interface{}, ctx context.Context, d return interceptor(ctx, in, info, handler) } +func _HeadscaleService_Health_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HealthRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).Health(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_Health_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).Health(ctx, req.(*HealthRequest)) + } + return interceptor(ctx, in, info, handler) +} + // HeadscaleService_ServiceDesc is the grpc.ServiceDesc for HeadscaleService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -999,6 +1035,10 @@ var HeadscaleService_ServiceDesc = grpc.ServiceDesc{ MethodName: "SetPolicy", Handler: _HeadscaleService_SetPolicy_Handler, }, + { + MethodName: "Health", + Handler: _HeadscaleService_Health_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "headscale/v1/headscale.proto", diff --git a/gen/openapiv2/headscale/v1/headscale.swagger.json b/gen/openapiv2/headscale/v1/headscale.swagger.json index c55dc077..66fb4850 100644 --- a/gen/openapiv2/headscale/v1/headscale.swagger.json +++ b/gen/openapiv2/headscale/v1/headscale.swagger.json @@ -164,6 +164,29 @@ ] } }, + "/api/v1/health": { + "get": { + "summary": "--- Health start ---", + "operationId": "HeadscaleService_Health", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1HealthResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "HeadscaleService" + ] + } + }, "/api/v1/node": { "get": { "operationId": "HeadscaleService_ListNodes", @@ -1056,6 +1079,22 @@ } } }, + "v1HealthResponse": { + "type": "object", + "properties": { + "status": { + "$ref": "#/definitions/v1HealthStatus" + } + } + }, + "v1HealthStatus": { + "type": "string", + "enum": [ + "Fail", + "Pass" + ], + "default": "Fail" + }, "v1ListApiKeysResponse": { "type": "object", "properties": { diff --git a/hscontrol/grpcv1.go b/hscontrol/grpcv1.go index 277e729d..e874942c 100644 --- a/hscontrol/grpcv1.go +++ b/hscontrol/grpcv1.go @@ -819,4 +819,18 @@ func (api headscaleV1APIServer) DebugCreateNode( return &v1.DebugCreateNodeResponse{Node: newNode.Node.Proto()}, nil } +func (api headscaleV1APIServer) Health( + ctx context.Context, + request *v1.HealthRequest, +) (*v1.HealthResponse, error) { + if err := api.h.state.PingDB(ctx); err != nil { + log.Error(). + Err(err). + Msg("Health check failed: database ping failed") + return &v1.HealthResponse{Status: v1.HealthStatus_Fail}, nil + } + + return &v1.HealthResponse{Status: v1.HealthStatus_Pass}, nil +} + func (api headscaleV1APIServer) mustEmbedUnimplementedHeadscaleServiceServer() {} diff --git a/proto/headscale/v1/headscale.proto b/proto/headscale/v1/headscale.proto index 7e0672bb..d25dc045 100644 --- a/proto/headscale/v1/headscale.proto +++ b/proto/headscale/v1/headscale.proto @@ -182,6 +182,14 @@ service HeadscaleService { } // --- Policy end --- + // --- Health start --- + rpc Health(HealthRequest) returns (HealthResponse) { + option (google.api.http) = { + get : "/api/v1/health" + }; + } + // --- Health end --- + // Implement Tailscale API // rpc GetDevice(GetDeviceRequest) returns(GetDeviceResponse) { // option(google.api.http) = { @@ -209,3 +217,14 @@ service HeadscaleService { // }; // } } + +enum HealthStatus { + Fail = 0; + Pass = 1; +} + +message HealthRequest {} + +message HealthResponse { + HealthStatus status = 1; +}