API keys, pre-auth keys and OAuth clients/tokens share one table and verify
path. Secrets carry 256 bits of crypto/rand entropy, so a SHA-256 digest
needs no stretching; bcrypt/argon2id rows rehash on use until 0.32.
The error only said the user still has nodes, which the CLI prompt did
not mention at all. Wrap ErrUserStillHasNodes with the ID and hostname
of every blocking node so the operator knows what to remove. Run the
DestroyUser test table on Postgres as well as SQLite, since the two
schemas define different foreign-key actions; the Postgres variant
skips without a local server.
resolveSingleUser reported every non-single result as "multiple users
match query", including zero matches. An explicit --identifier 0 was
sent to the API, which treats id=0 as no filter, so it listed every
user and failed as ambiguous. Return a not-found error for zero matches,
list the matching users when several match, and reject a non-positive
identifier before calling the API.
Stand up a nix-built headscale over self-signed TLS with embedded DERP and a
regular node joined via pre-auth, then drive the official tailscale/github-action
against it: connect over OAuth (tskey-client-) and an auth key with ping as the
success gate, and exercise its hostname, version, tailscaled-args, statedir and
args inputs.
The upstream tailscale client only runs its OAuth client-credentials exchange
for secrets prefixed tskey-client-, so accept it as an alias for hskey-client-.
The prefix is only a label sliced off before lookup, so the same stored client
authenticates under either; lets the official client and GitHub Action mint auth
keys against headscale.
Auto-close PRs from authors not listed in .github/VOUCHED.td. Issues
stay open to everyone; the close message points contributors at
CONTRIBUTING.md. Maintainers manage the list with !vouch, !denounce
and !unvouch comments, and a weekly job resyncs CODEOWNERS.
The workflows use GITHUB_TOKEN with explicit least-privilege
permissions.
Diffing go.mod catches a downgrade wherever selection produced it;
reading what go get printed only catches what go get did itself. The
lockstep partners are exempt, since repin lowers them on purpose.
`go get -u` takes the highest semver the proxy offers: a fork's stray tag
sorting above its real branch, or a module that has moved and kept
tagging under the old path. Resolving first refuses both, and names the
compare link for every version that does move.
The pre-commit hooks kept their own list of formatters, so nixpkgs-fmt in
a hook and nixfmt in the check disagreed the moment flake-checks moved.
`nix fmt` is now built from the same treefmt module the formatting check
is, and the hook and the Makefile both call it.
gateFlake judges the formatting check right after the lock moves, but the
fmt area runs last, so the new toolchain's formatting had not been applied
yet and the flake area dropped itself every run.
flake-checks moved its treefmt Nix formatter to nixfmt, so every .nix file
failed the formatting check. The devShell and the pre-commit hook shipped
nixpkgs-fmt, which reformatted them straight back.
flake-checks moved its nix formatter from nixpkgs-fmt to nixfmt, so every
.nix file failed the formatting check. gateFlake only ran `nix eval`, so
that reached the final gate, which drops the newest commit first and had
to pop all eleven areas above it to get there. Three hours, nothing
shipped.
prettier has no TOML parser, so `prek run --all-files` failed on
.mdformat.toml with "No parser could be inferred". The Makefile's own
prettier glob never included toml.
golang.org/x/net deprecated http2.Server, staticcheck failed on
hscontrol/noise.go, and the whole batch of 50 direct requirements was
dropped for it. The bisect tries every atom together first, so one atom
per module costs nothing until something actually breaks.
nixpkgs decides which prettier and gofumpt format the tree. A lock bump to
prettier 3.9.6 reformatted CHANGELOG.md, which no area had touched, and the
formatting check failed on a file the bot never edited.
go env GOVERSION reports the toolchain the go command switched to, so a
go.mod that had outrun nixpkgs read back as a nixpkgs that had caught
up, and checkToolchain compared 1.27.1 against itself.
tailscale.com@main raised the go directive to 1.27.1 while nixpkgs was
still on 1.27.0. go build downloads the newer toolchain and looks fine;
the nix builders set GOTOOLCHAIN=local and fail, so the whole bump was
being thrown away one area at a time.
The oapi-codegen pin stays the single source of truth: the generate area
reads it back out of the Makefile, so the clients are regenerated with
whatever this lands on.
Debian is resolved from the numeric tags, which exist only for released
versions: forky-slim is published today and is testing. Distroless
follows that same release rather than its own repository names, since
gcr answers for base-debian99 as readily as for base-debian13.
Keeps the interlocked pins current and reports what it could not move.
Areas apply, gate and commit one at a time, so a dependency that breaks
the build costs one commit rather than the whole pull request.
Self-hosted-runner shape (repositories, platform, gitAuthor) that a
repo-level config cannot set, and nothing has run it since the workflow
was removed in 6a311f4ab.
Both stages compile a tailscale tree cloned from an unpinned branch. The
golang images set GOTOOLCHAIN=local, so the moment upstream raises its go
directive the build fails outright instead of fetching a toolchain.
Neither file was listed in any paths-filter, so a lockfile-only change
skipped build, check-generated, check-tests and the whole integration
matrix. Also drops integration_test/, which has never existed.
#3472 and #3487 merged without a changelog entry, and the #3409 and
peer map entries landed under 0.30.0 although they ship in 0.29.4.
Sets the release date.
Sub-builders called after validateServerConfig (prefixV4, prefixV6,
allocation strategy, dns, oidc client secret/path, isSafeServerURL)
each returned the first error. So an operator that fixed one
issue saw the next one only on the next startup.
Lift one *configValidator across the entire LoadServerConfig flow.
validateServerConfigInto(v) populates it; each sub-builder's error
is wrapped in a structured *ConfigError (with the original sentinel
kept on the Cause field, so errors.Is against errOidcMutuallyExclusive,
errServerURLSame/Suffix, ErrNoPrefixConfigured, and
ErrInvalidAllocationStrategy keeps working). v.Err() is checked once,
right before the &Config{} construction.
TestReadConfig/base-domain-in-server-url-err matched the old sentinel
wording; flipped to the new structured Reason. Added
TestLoadServerConfig_CollectsAcrossSubBuilders to lock the wiring:
four sub-builder failures from a single config produce four
*ConfigErrors in the report.
Updates #3227