diff --git a/CHANGELOG.md b/CHANGELOG.md index e3957b80..21d97209 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ systemctl start headscale [#2614](https://github.com/juanfont/headscale/pull/2614) - Support client verify for DERP [#2046](https://github.com/juanfont/headscale/pull/2046) +- Add healthcheck command [#2659](https://github.com/juanfont/headscale/pull/2659) - Remove redundant check regarding `noise` config [#2658](https://github.com/juanfont/headscale/pull/2658) - Refactor OpenID Connect documentation diff --git a/cmd/headscale/cli/health.go b/cmd/headscale/cli/health.go new file mode 100644 index 00000000..864724cc --- /dev/null +++ b/cmd/headscale/cli/health.go @@ -0,0 +1,29 @@ +package cli + +import ( + 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) + } + + SuccessOutput(response, "", output) + }, +} diff --git a/gen/go/headscale/v1/headscale.pb.go b/gen/go/headscale/v1/headscale.pb.go index aa3380c6..013938f4 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,94 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +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"` + DatabaseConnectivity bool `protobuf:"varint,1,opt,name=database_connectivity,json=databaseConnectivity,proto3" json:"database_connectivity,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) GetDatabaseConnectivity() bool { + if x != nil { + return x.DatabaseConnectivity + } + return false +} + 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\"E\n" + + "\x0eHealthResponse\x123\n" + + "\x15database_connectivity\x18\x01 \x01(\bR\x14databaseConnectivity2\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,109 +140,127 @@ 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_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 + (*HealthRequest)(nil), // 0: headscale.v1.HealthRequest + (*HealthResponse)(nil), // 1: headscale.v1.HealthResponse + (*CreateUserRequest)(nil), // 2: headscale.v1.CreateUserRequest + (*RenameUserRequest)(nil), // 3: headscale.v1.RenameUserRequest + (*DeleteUserRequest)(nil), // 4: headscale.v1.DeleteUserRequest + (*ListUsersRequest)(nil), // 5: headscale.v1.ListUsersRequest + (*CreatePreAuthKeyRequest)(nil), // 6: headscale.v1.CreatePreAuthKeyRequest + (*ExpirePreAuthKeyRequest)(nil), // 7: headscale.v1.ExpirePreAuthKeyRequest + (*ListPreAuthKeysRequest)(nil), // 8: headscale.v1.ListPreAuthKeysRequest + (*DebugCreateNodeRequest)(nil), // 9: headscale.v1.DebugCreateNodeRequest + (*GetNodeRequest)(nil), // 10: headscale.v1.GetNodeRequest + (*SetTagsRequest)(nil), // 11: headscale.v1.SetTagsRequest + (*SetApprovedRoutesRequest)(nil), // 12: headscale.v1.SetApprovedRoutesRequest + (*RegisterNodeRequest)(nil), // 13: headscale.v1.RegisterNodeRequest + (*DeleteNodeRequest)(nil), // 14: headscale.v1.DeleteNodeRequest + (*ExpireNodeRequest)(nil), // 15: headscale.v1.ExpireNodeRequest + (*RenameNodeRequest)(nil), // 16: headscale.v1.RenameNodeRequest + (*ListNodesRequest)(nil), // 17: headscale.v1.ListNodesRequest + (*MoveNodeRequest)(nil), // 18: headscale.v1.MoveNodeRequest + (*BackfillNodeIPsRequest)(nil), // 19: headscale.v1.BackfillNodeIPsRequest + (*CreateApiKeyRequest)(nil), // 20: headscale.v1.CreateApiKeyRequest + (*ExpireApiKeyRequest)(nil), // 21: headscale.v1.ExpireApiKeyRequest + (*ListApiKeysRequest)(nil), // 22: headscale.v1.ListApiKeysRequest + (*DeleteApiKeyRequest)(nil), // 23: headscale.v1.DeleteApiKeyRequest + (*GetPolicyRequest)(nil), // 24: headscale.v1.GetPolicyRequest + (*SetPolicyRequest)(nil), // 25: headscale.v1.SetPolicyRequest + (*CreateUserResponse)(nil), // 26: headscale.v1.CreateUserResponse + (*RenameUserResponse)(nil), // 27: headscale.v1.RenameUserResponse + (*DeleteUserResponse)(nil), // 28: headscale.v1.DeleteUserResponse + (*ListUsersResponse)(nil), // 29: headscale.v1.ListUsersResponse + (*CreatePreAuthKeyResponse)(nil), // 30: headscale.v1.CreatePreAuthKeyResponse + (*ExpirePreAuthKeyResponse)(nil), // 31: headscale.v1.ExpirePreAuthKeyResponse + (*ListPreAuthKeysResponse)(nil), // 32: headscale.v1.ListPreAuthKeysResponse + (*DebugCreateNodeResponse)(nil), // 33: headscale.v1.DebugCreateNodeResponse + (*GetNodeResponse)(nil), // 34: headscale.v1.GetNodeResponse + (*SetTagsResponse)(nil), // 35: headscale.v1.SetTagsResponse + (*SetApprovedRoutesResponse)(nil), // 36: headscale.v1.SetApprovedRoutesResponse + (*RegisterNodeResponse)(nil), // 37: headscale.v1.RegisterNodeResponse + (*DeleteNodeResponse)(nil), // 38: headscale.v1.DeleteNodeResponse + (*ExpireNodeResponse)(nil), // 39: headscale.v1.ExpireNodeResponse + (*RenameNodeResponse)(nil), // 40: headscale.v1.RenameNodeResponse + (*ListNodesResponse)(nil), // 41: headscale.v1.ListNodesResponse + (*MoveNodeResponse)(nil), // 42: headscale.v1.MoveNodeResponse + (*BackfillNodeIPsResponse)(nil), // 43: headscale.v1.BackfillNodeIPsResponse + (*CreateApiKeyResponse)(nil), // 44: headscale.v1.CreateApiKeyResponse + (*ExpireApiKeyResponse)(nil), // 45: headscale.v1.ExpireApiKeyResponse + (*ListApiKeysResponse)(nil), // 46: headscale.v1.ListApiKeysResponse + (*DeleteApiKeyResponse)(nil), // 47: headscale.v1.DeleteApiKeyResponse + (*GetPolicyResponse)(nil), // 48: headscale.v1.GetPolicyResponse + (*SetPolicyResponse)(nil), // 49: 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 + 2, // 0: headscale.v1.HeadscaleService.CreateUser:input_type -> headscale.v1.CreateUserRequest + 3, // 1: headscale.v1.HeadscaleService.RenameUser:input_type -> headscale.v1.RenameUserRequest + 4, // 2: headscale.v1.HeadscaleService.DeleteUser:input_type -> headscale.v1.DeleteUserRequest + 5, // 3: headscale.v1.HeadscaleService.ListUsers:input_type -> headscale.v1.ListUsersRequest + 6, // 4: headscale.v1.HeadscaleService.CreatePreAuthKey:input_type -> headscale.v1.CreatePreAuthKeyRequest + 7, // 5: headscale.v1.HeadscaleService.ExpirePreAuthKey:input_type -> headscale.v1.ExpirePreAuthKeyRequest + 8, // 6: headscale.v1.HeadscaleService.ListPreAuthKeys:input_type -> headscale.v1.ListPreAuthKeysRequest + 9, // 7: headscale.v1.HeadscaleService.DebugCreateNode:input_type -> headscale.v1.DebugCreateNodeRequest + 10, // 8: headscale.v1.HeadscaleService.GetNode:input_type -> headscale.v1.GetNodeRequest + 11, // 9: headscale.v1.HeadscaleService.SetTags:input_type -> headscale.v1.SetTagsRequest + 12, // 10: headscale.v1.HeadscaleService.SetApprovedRoutes:input_type -> headscale.v1.SetApprovedRoutesRequest + 13, // 11: headscale.v1.HeadscaleService.RegisterNode:input_type -> headscale.v1.RegisterNodeRequest + 14, // 12: headscale.v1.HeadscaleService.DeleteNode:input_type -> headscale.v1.DeleteNodeRequest + 15, // 13: headscale.v1.HeadscaleService.ExpireNode:input_type -> headscale.v1.ExpireNodeRequest + 16, // 14: headscale.v1.HeadscaleService.RenameNode:input_type -> headscale.v1.RenameNodeRequest + 17, // 15: headscale.v1.HeadscaleService.ListNodes:input_type -> headscale.v1.ListNodesRequest + 18, // 16: headscale.v1.HeadscaleService.MoveNode:input_type -> headscale.v1.MoveNodeRequest + 19, // 17: headscale.v1.HeadscaleService.BackfillNodeIPs:input_type -> headscale.v1.BackfillNodeIPsRequest + 20, // 18: headscale.v1.HeadscaleService.CreateApiKey:input_type -> headscale.v1.CreateApiKeyRequest + 21, // 19: headscale.v1.HeadscaleService.ExpireApiKey:input_type -> headscale.v1.ExpireApiKeyRequest + 22, // 20: headscale.v1.HeadscaleService.ListApiKeys:input_type -> headscale.v1.ListApiKeysRequest + 23, // 21: headscale.v1.HeadscaleService.DeleteApiKey:input_type -> headscale.v1.DeleteApiKeyRequest + 24, // 22: headscale.v1.HeadscaleService.GetPolicy:input_type -> headscale.v1.GetPolicyRequest + 25, // 23: headscale.v1.HeadscaleService.SetPolicy:input_type -> headscale.v1.SetPolicyRequest + 0, // 24: headscale.v1.HeadscaleService.Health:input_type -> headscale.v1.HealthRequest + 26, // 25: headscale.v1.HeadscaleService.CreateUser:output_type -> headscale.v1.CreateUserResponse + 27, // 26: headscale.v1.HeadscaleService.RenameUser:output_type -> headscale.v1.RenameUserResponse + 28, // 27: headscale.v1.HeadscaleService.DeleteUser:output_type -> headscale.v1.DeleteUserResponse + 29, // 28: headscale.v1.HeadscaleService.ListUsers:output_type -> headscale.v1.ListUsersResponse + 30, // 29: headscale.v1.HeadscaleService.CreatePreAuthKey:output_type -> headscale.v1.CreatePreAuthKeyResponse + 31, // 30: headscale.v1.HeadscaleService.ExpirePreAuthKey:output_type -> headscale.v1.ExpirePreAuthKeyResponse + 32, // 31: headscale.v1.HeadscaleService.ListPreAuthKeys:output_type -> headscale.v1.ListPreAuthKeysResponse + 33, // 32: headscale.v1.HeadscaleService.DebugCreateNode:output_type -> headscale.v1.DebugCreateNodeResponse + 34, // 33: headscale.v1.HeadscaleService.GetNode:output_type -> headscale.v1.GetNodeResponse + 35, // 34: headscale.v1.HeadscaleService.SetTags:output_type -> headscale.v1.SetTagsResponse + 36, // 35: headscale.v1.HeadscaleService.SetApprovedRoutes:output_type -> headscale.v1.SetApprovedRoutesResponse + 37, // 36: headscale.v1.HeadscaleService.RegisterNode:output_type -> headscale.v1.RegisterNodeResponse + 38, // 37: headscale.v1.HeadscaleService.DeleteNode:output_type -> headscale.v1.DeleteNodeResponse + 39, // 38: headscale.v1.HeadscaleService.ExpireNode:output_type -> headscale.v1.ExpireNodeResponse + 40, // 39: headscale.v1.HeadscaleService.RenameNode:output_type -> headscale.v1.RenameNodeResponse + 41, // 40: headscale.v1.HeadscaleService.ListNodes:output_type -> headscale.v1.ListNodesResponse + 42, // 41: headscale.v1.HeadscaleService.MoveNode:output_type -> headscale.v1.MoveNodeResponse + 43, // 42: headscale.v1.HeadscaleService.BackfillNodeIPs:output_type -> headscale.v1.BackfillNodeIPsResponse + 44, // 43: headscale.v1.HeadscaleService.CreateApiKey:output_type -> headscale.v1.CreateApiKeyResponse + 45, // 44: headscale.v1.HeadscaleService.ExpireApiKey:output_type -> headscale.v1.ExpireApiKeyResponse + 46, // 45: headscale.v1.HeadscaleService.ListApiKeys:output_type -> headscale.v1.ListApiKeysResponse + 47, // 46: headscale.v1.HeadscaleService.DeleteApiKey:output_type -> headscale.v1.DeleteApiKeyResponse + 48, // 47: headscale.v1.HeadscaleService.GetPolicy:output_type -> headscale.v1.GetPolicyResponse + 49, // 48: headscale.v1.HeadscaleService.SetPolicy:output_type -> headscale.v1.SetPolicyResponse + 1, // 49: headscale.v1.HeadscaleService.Health:output_type -> headscale.v1.HealthResponse + 25, // [25:50] is the sub-list for method output_type + 0, // [0:25] 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 @@ -180,12 +282,13 @@ func file_headscale_v1_headscale_proto_init() { 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, + NumMessages: 2, NumExtensions: 0, NumServices: 1, }, GoTypes: file_headscale_v1_headscale_proto_goTypes, DependencyIndexes: file_headscale_v1_headscale_proto_depIdxs, + 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..2900d65f 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,14 @@ } } }, + "v1HealthResponse": { + "type": "object", + "properties": { + "databaseConnectivity": { + "type": "boolean" + } + } + }, "v1ListApiKeysResponse": { "type": "object", "properties": { diff --git a/hscontrol/grpcv1.go b/hscontrol/grpcv1.go index 722f8421..abe5728d 100644 --- a/hscontrol/grpcv1.go +++ b/hscontrol/grpcv1.go @@ -769,4 +769,24 @@ 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) { + var healthErr error + response := &v1.HealthResponse{} + + if err := api.h.state.PingDB(ctx); err != nil { + healthErr = fmt.Errorf("database ping failed: %w", err) + } else { + response.DbPing = true + } + + if healthErr != nil { + log.Error().Err(healthErr).Msg("Health check failed") + } + + return response, healthErr +} + func (api headscaleV1APIServer) mustEmbedUnimplementedHeadscaleServiceServer() {} diff --git a/proto/headscale/v1/headscale.proto b/proto/headscale/v1/headscale.proto index 7e0672bb..3b42a3f3 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,9 @@ service HeadscaleService { // }; // } } + +message HealthRequest {} + +message HealthResponse { + bool database_connectivity = 1; +}