diff --git a/AGENTS.md b/AGENTS.md index 7fc325c6..000e5d1e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -133,9 +133,11 @@ headscale/ ### `hscontrol/` packages -- `app.go`, `handlers.go`, `grpcv1.go`, `noise.go`, `auth.go`, `oidc.go`, +- `app.go`, `handlers.go`, `noise.go`, `auth.go`, `oidc.go`, `poll.go`, `metrics.go`, `debug.go`, `tailsql.go`, `platform_config.go` — top-level server files +- `api/v1/` — the ogen-backed v1 HTTP API handlers (`server.go`, + `convert.go`, `errors.go`, per-resource handler files) - `state/` — central coordinator (`state.go`) and the copy-on-write `NodeStore` (`node_store.go`). All cross-subsystem operations go through `State`. @@ -209,7 +211,7 @@ both. This is a load-bearing architectural rule. - `SetTags` validation is enforced by `validateNodeOwnership()` in `hscontrol/state/tags.go`. - Examples and edge cases live in `hscontrol/types/node_tags_test.go` - and `hscontrol/grpcv1_test.go` (`TestSetTags_*`). + and the SetTags tests in `hscontrol/servertest/apiv1_nodes_test.go`. **Don't do this**: diff --git a/cmd/dev/README.md b/cmd/dev/README.md index 46da415d..f703f442 100644 --- a/cmd/dev/README.md +++ b/cmd/dev/README.md @@ -42,8 +42,7 @@ go tool mts node1 status | `--port` | 8080 | Headscale listen port | | `--keep` | false | Keep state directory on exit | -The metrics/debug port is `port + 1010` (default 9090) and the gRPC -port is `port + 42363` (default 50443). +The metrics/debug port is `port + 1010` (default 9090). ## What it does diff --git a/cmd/headscale/cli/client.go b/cmd/headscale/cli/client.go index 36c97585..c582f19e 100644 --- a/cmd/headscale/cli/client.go +++ b/cmd/headscale/cli/client.go @@ -96,7 +96,7 @@ func localSocketClient(socketPath string) (*apiv1.Client, error) { // dialSocketWaiting connects to the local unix socket, retrying while it is // still absent. headscale removes and rebinds the socket during startup, so a // CLI command issued right after "systemctl start" can beat the server to it. -// This mirrors the old blocking gRPC dial, which retried until the CLI timeout. +// This mirrors the old blocking dial, which retried until the CLI timeout. func dialSocketWaiting(ctx context.Context, socketPath string) (net.Conn, error) { var dialer net.Dialer diff --git a/hscontrol/api/v1/apikeys.go b/hscontrol/api/v1/apikeys.go index 81bb4f13..5109285c 100644 --- a/hscontrol/api/v1/apikeys.go +++ b/hscontrol/api/v1/apikeys.go @@ -77,7 +77,7 @@ func (s *Server) DeleteApiKey(_ context.Context, params oas.DeleteApiKeyParams) } // apiKeyByIDOrPrefix looks up an API key by exactly one of id or prefix. -// Providing neither or both is a 400, matching the gRPC contract. +// Providing neither or both is a 400. func (s *Server) apiKeyByIDOrPrefix( id uint64, prefix string, diff --git a/hscontrol/api/v1/convert.go b/hscontrol/api/v1/convert.go index a28cf2f8..588f2a92 100644 --- a/hscontrol/api/v1/convert.go +++ b/hscontrol/api/v1/convert.go @@ -11,12 +11,11 @@ import ( // This file converts the state-layer types into the ogen API types. It reads // the copy-on-write view types (NodeView, UserView, PreAuthKeyView) directly so -// no node/user is deep-copied on the read path, and reproduces exactly what the -// previous proto builders emitted — username fallback, masked key prefixes, -// online computation, the register-method enum. +// no node/user is deep-copied on the read path, and reproduces the same fields +// the previous API emitted — username fallback, masked key prefixes, online +// computation, the register-method enum. // -// Unlike grpc-gateway (which marshalled with EmitUnpopulated), these converters -// omit zero-value and absent fields — empty strings, false booleans, zero +// These converters omit zero-value and absent fields — empty strings, false booleans, zero // numbers, empty arrays, nil timestamps/objects, and the unspecified register // method. See docs/v1-ogen/CHANGES.md. diff --git a/hscontrol/api/v1/health.go b/hscontrol/api/v1/health.go index 2e23d09b..92e952d5 100644 --- a/hscontrol/api/v1/health.go +++ b/hscontrol/api/v1/health.go @@ -7,7 +7,7 @@ import ( ) // Health reports server health, including database connectivity. A failed -// database ping is a 500; the gRPC implementation likewise returned the ping +// database ping is a 500; the previous implementation likewise returned the ping // error (the body's databaseConnectivity flag was never observable on failure). func (s *Server) Health(ctx context.Context) (*oas.HealthOK, error) { err := s.state.PingDB(ctx) diff --git a/hscontrol/api/v1/server.go b/hscontrol/api/v1/server.go index dcecb4f6..b421367b 100644 --- a/hscontrol/api/v1/server.go +++ b/hscontrol/api/v1/server.go @@ -19,9 +19,8 @@ import ( // Server implements the generated [oas.Handler] and [oas.SecurityHandler]. // -// Operations that have not been migrated from the gRPC stack yet are inherited -// from [oas.UnimplementedHandler] and return 501; each resource group replaces -// its stubs as it is converted. +// Any operation not implemented here is inherited from +// [oas.UnimplementedHandler] and returns 501; every operation is implemented. type Server struct { oas.UnimplementedHandler diff --git a/hscontrol/auth_test.go b/hscontrol/auth_test.go index ed294f3c..e46aa970 100644 --- a/hscontrol/auth_test.go +++ b/hscontrol/auth_test.go @@ -4052,7 +4052,7 @@ func TestHandleNodeFromPreAuthKey_OldUserNil_NoPanic(t *testing.T) { } // TestHandleNodeFromAuthPath_OldUserNil_NoPanic is the parallel guard -// for the gRPC/OIDC entry point. Same orphan shape as +// for the OIDC/auth-path entry point. Same orphan shape as // TestHandleNodeFromPreAuthKey_OldUserNil_NoPanic; HandleNodeFromAuthPath // has its own oldUser.Name() log line in the existingNodeOwnedByOtherUser // branch and panics independently of the noise registration path. diff --git a/hscontrol/servertest/apiv1_users_test.go b/hscontrol/servertest/apiv1_users_test.go index bf4ee016..324ce889 100644 --- a/hscontrol/servertest/apiv1_users_test.go +++ b/hscontrol/servertest/apiv1_users_test.go @@ -72,7 +72,7 @@ func TestAPIv1_RenameUser(t *testing.T) { _, err = srv.State().GetUserByName("alice2") require.NoError(t, err) - // Unknown user is a 404 (the gRPC stack returned 500 here). + // Unknown user is a 404 (the previous implementation returned 500 here). _, err = client.RenameUser(ctx, apiv1.RenameUserParams{OldID: 99999, NewName: "ghost"}) requireProblem(t, err, http.StatusNotFound) } diff --git a/hscontrol/state/persist_test.go b/hscontrol/state/persist_test.go index 0be0b411..43d6cbf6 100644 --- a/hscontrol/state/persist_test.go +++ b/hscontrol/state/persist_test.go @@ -83,7 +83,7 @@ func persistTestConfig(dbPath string) *types.Config { } // TestPersistEmptyApprovedRoutes covers the State.SetApprovedRoutes -// path. The gRPC handler builds the slice via append from a nil +// path. The handler builds the slice via append from a nil // declaration, so when the operator passes `-r ""` the persist layer // receives a nil []netip.Prefix. GORM's struct Updates skips nil // slices, so the column would stay populated with the previously diff --git a/hscontrol/util/zlog/zf/fields.go b/hscontrol/util/zlog/zf/fields.go index 45edd1da..6661d8fe 100644 --- a/hscontrol/util/zlog/zf/fields.go +++ b/hscontrol/util/zlog/zf/fields.go @@ -155,7 +155,7 @@ const ( Address = "address" ) -// gRPC fields. +// Request fields. const ( Client = "client" Request = "request" diff --git a/integration/README.md b/integration/README.md index 5511a113..e4e4a518 100644 --- a/integration/README.md +++ b/integration/README.md @@ -159,7 +159,7 @@ for _, client := range allClients { Typical blocking operations: any `tailscale set` (routes, exit node, accept-routes, ssh), node registration via the CLI, user creation via -gRPC. +the CLI. ### The four rules diff --git a/integration/acl_test.go b/integration/acl_test.go index b8be400b..9f9007c3 100644 --- a/integration/acl_test.go +++ b/integration/acl_test.go @@ -2634,7 +2634,7 @@ func TestACLTagPropagation(t *testing.T) { // Step 3: Verify final NetMap visibility first (fast signal that // the [tailcfg.MapResponse] propagated to the client). - // The full propagation chain (docker exec → gRPC → state update → + // The full propagation chain (docker exec → API → state update → // batcher delay → [tailcfg.MapResponse] → noise transport → client processing) // can take over 120s on congested CI runners, so use a generous // base timeout. diff --git a/openapi/v1/headscale.yaml b/openapi/v1/headscale.yaml index 635495f4..e20d3a06 100644 --- a/openapi/v1/headscale.yaml +++ b/openapi/v1/headscale.yaml @@ -5,9 +5,8 @@ info: description: | HTTP API for managing a Headscale control server: users, pre-auth keys, nodes, API keys, and ACL policy. This is the v1 API, served under - `/api/v1`. It is the OpenAPI 3.0 successor to the previous - gRPC/grpc-gateway facade and is the single source of truth for the API - contract. + `/api/v1`. It is the OpenAPI 3.0 definition and the single source of truth + for the API contract. Authentication is a bearer API key (`Authorization: Bearer `), except over the local unix socket where filesystem permissions are the