Commit Graph

4541 Commits

Author SHA1 Message Date
Kristoffer Dalby d59c1ed209 flake: format the tree with one treefmt
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.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby f1186d8e00 tools/bump: reformat inside the lock bump
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.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby 8d89742a21 flake: format Nix with nixfmt
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.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby 91935139ba tools/bump: judge the lock bump against the real checks
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.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby e8797dd6df .pre-commit-config: stop handing .toml files to prettier
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.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby 4657a8e8af tools/bump: run the pre-commit hooks before opening the pull request
make fmt does not cover nixpkgs-fmt, trailing whitespace, end-of-file or
the YAML checks, and the bot rewrites workflow files and Dockerfiles.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby 8ed39eeb0c tools/bump: bisect go.mod one module at a time
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.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby 2cf100360d tools/bump: reformat after the toolchain moves
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.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby d33ed80c41 tools/bump: read the toolchain with GOTOOLCHAIN=local
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.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby c7a7ea20ce AGENTS.md: document the version bump rules
The three interlocks a hand-written bump has to respect, and where to
read what the automation would do.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby 1449b95f1e tools/bump: drop dependencies that outrun the nixpkgs Go
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.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby 30d10640ea tools/bump: reword the verify failure
"invariant" is not language this repository uses.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby 60c5f8277f .github: fold the action version updater into tools/bump
The standalone workflow has failed weekly since its token secret went
missing. One bot, one credential, one pull request to review.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby 24e43d0a5a tools/bump: bump the pinned developer tools
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.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby 08c7ca0fa2 tools/bump: bump the remaining container base images
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.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby 611a70dff2 tools/bump: say which gates actually ran
The body claimed the nix checks had run regardless of --gate, which is
exactly the sort of thing a reviewer takes at face value.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby 57034c314a .github: run tools/bump on demand
Manual dispatch only until the pull requests have been trusted for a
while; the schedule comes after.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby c3d771e23e tools/bump: add the version bump tool
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.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby 23cb9883f5 go.mod: promote golang.org/x/mod to a direct requirement
tools/bump parses go.mod files with it.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby 9d932c4427 .github: point CODEOWNERS at the real goreleaser file
The tracked file is .goreleaser.yml, so the .yaml rule matched nothing.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby be20f006e2 .github: delete the unused renovate config
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.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby f17f1f561b flake: drop the Go version from the go_latest comment
The attribute tracks nixpkgs; the literal only drifts.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby 562d7f5899 flake: add gh to the devShell 2026-09-25 15:52:26 +02:00
Kristoffer Dalby f06b45223f tools/capver: drop the duplicate go:generate directive
hscontrol/capver/capver.go already points here, so go generate ./... was
scraping the tailscale tag list twice.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby 507046bb7d tools/capver: exit non-zero on failure
A failed tag fetch left the generated files untouched, which is
indistinguishable from no drift: check-generated passed on stale output.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby e40f47abbf docker: let the tailscale builders fetch a newer toolchain
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.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby 0c1c8bdcf4 .github: gate CI on flake.lock and flakehashes.json
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.
2026-09-25 15:52:26 +02:00
Kristoffer Dalby f227d68781 CHANGELOG: align the 0.29.4 section with the release branch
#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.
2026-09-23 21:22:20 +02:00
Igor Serganov 0661c6f540 db: return non-NotFound errors from user lookups (#3480) 2026-09-23 15:24:57 +02:00
Michael Lopez fdcdebb392 db: delete pre-auth key on its own savepoint 2026-09-23 15:24:22 +02:00
Kristoffer Dalby 4cad0b564e types: lift configValidator across LoadServerConfig sub-builders
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
2026-09-23 15:18:34 +02:00
Kristoffer Dalby 8a12c572ef types: route deprecation fatals through configValidator
deprecator.Log() called log.Fatal directly, so a single deprecated
key killed the process before any other validation rule could
report. Operators fixing one issue at a time.

Replace Log() with Apply(*configValidator). Warns still go to
log.Warn; fatals are pushed onto the validator as *ConfigError so
they merge with the rest of the config-load report. The free-form
strings collected via set.Set are gone; deprecator now stores
typed deprecation{OldKey, NewKey} records and Apply renders them
into the structured ConfigError shape.

Move the early-return PKCE check into a new validatePKCEConfig
modular validator for the same reason.

Updates #3227
2026-09-23 15:18:34 +02:00
Kristoffer Dalby 435cf74d9e changelog: document config validation and listener error rework
Updates #3227
2026-09-23 15:18:34 +02:00
Kristoffer Dalby 616c9b6495 docs: warn about listen_addr / ACME port collision
Add a callout under the HTTP-01 docs so operators see the
constraint before they hit the validation error at startup.

Updates #3227
2026-09-23 15:18:34 +02:00
Kristoffer Dalby 2114ced0d5 cli: classify Serve errors with operator hints
A *ListenerBindError that wraps syscall.EADDRINUSE now ends with a
"sudo ss -tlnp 'sport = :PORT'" pointer, and one wrapping
syscall.EACCES with a CAP_NET_BIND_SERVICE / setcap pointer. The
underlying chain is preserved via fmt.Errorf("%w"), so errors.Is /
errors.As continue to walk to the typed bind error and the syscall
errno.

Drop the "headscale ran into an error and had to shut down" wrap,
which only restated the symptom.

Export types.PortFromAddr so the classifier can render the port
number in the hint.

Updates #3227
2026-09-23 15:18:34 +02:00
Kristoffer Dalby 61021c739a app: route ACME HTTP-01 listener through errgroup
The HTTP-01 challenge listener was launched in an orphan goroutine that
called log.Fatal on bind failure, which bypassed the signal-handler
shutdown path and made bind errors look identical to the main HTTP
listener.

Bind eagerly via net.ListenConfig in getTLSSettings, return the listener
+ http.Server in a tlsBundle, register the Serve loop with the existing
errgroup, and call Shutdown alongside the other servers. Bind failures
now surface as ListenerBindError(Listener:"ACME HTTP-01 challenge")
through the normal error chain.

Updates #3227
2026-09-23 15:18:34 +02:00
Kristoffer Dalby 37e1904924 hscontrol: name listener bind failures via ListenerBindError
Replace the three "binding to TCP address" wraps with typed
ListenerBindError values that carry the listener role, the YAML key
that drove the address, and the resolved address. The wrapped error
chain still walks to syscall.EADDRINUSE / EACCES so existing callers
that match those sentinels keep working; the difference is that the
operator now sees which listener failed instead of an unattributed
"binding to TCP address" line.

Updates #3227
2026-09-23 15:18:34 +02:00
Kristoffer Dalby 43ad10da52 types: add ListenerBindError for typed bind failures
ListenerBindError wraps a TCP listener bind failure with the listener
name and the YAML key that drove its address. Preserves the underlying
*net.OpError via Unwrap so errors.Is(err, syscall.EADDRINUSE) keeps
working through any number of fmt.Errorf("%w") wraps. Lets the
top-level CLI classifier match listener failures with errors.As
instead of string matching.

Updates #3227
2026-09-23 15:18:34 +02:00
Kristoffer Dalby 7865430419 types: move sub-builder log.Fatal sites into validators
derpConfig, databaseConfig, and dnsToTailcfgDNS used log.Fatal to
reject invalid combinations the moment they were observed. Lift those
checks into modular validators (validateDERPConfig,
validateDatabaseConfig, validateMagicDNSConfig) called from
validateServerConfig so each violation lands in the same configValidator
collector and renders as a structured ConfigError next to all other
config feedback. The sub-builder functions trust validation has run
and no longer crash the process.

Updates #3227
2026-09-23 15:18:34 +02:00
Kristoffer Dalby 1b4b79901a types: rework validateServerConfig to use ConfigError
Convert the errorText accumulator inside validateServerConfig to the
typed configValidator pattern. Each rule violation now renders as a
multi-line block naming the YAML key, the value the operator wrote,
and a hint pointing at the resolution. The dns.extra_records mutex
that previously crashed via log.Fatal is folded into the same
collector so an operator sees every problem in one go instead of
fixing them one startup attempt at a time.

Test wantErr assertions on validation output switch to substring
match because the rendered errors are now multi-line.

Updates #3227
2026-09-23 15:18:34 +02:00
Kristoffer Dalby 284a4891d7 types: validate listener address collisions
Reject configurations where two configured TCP listeners would bind
the same kernel socket. Covers every pair of listen_addr,
grpc_listen_addr, metrics_listen_addr, and tls_letsencrypt_listen
(when HTTP-01 ACME is in use). Each violation renders as a structured
ConfigError naming both YAML keys, the values the operator wrote, and
a hint pointing at the canonical setup.

Closes the symptom in #3227: the misconfiguration is now rejected at
config-load time with a self-explaining error, instead of failing at
runtime with a misleading "address already in use" log because the
ACME challenge listener and the main HTTP listener competed for the
same port inside the same process.

Fixes #3227
2026-09-23 15:18:34 +02:00
Kristoffer Dalby 6031194c36 types: add listener address helpers
portFromAddr resolves numeric and named ports (":http", ":https")
without touching /etc/services or the resolver. listenersOverlap
follows kernel rules: same port plus a wildcard host on either side,
or same port plus identical specific host, both count as collision;
different specific hosts on the same port do not. Set an explicit
viper default for tls_letsencrypt_listen so a minimal config still
resolves to ":http".

Updates #3227
2026-09-23 15:18:34 +02:00
Kristoffer Dalby 9f9fd0d885 types: add structured ConfigError and validator
Foundation for restructuring config validation feedback. Typed error
with errors.Is/As/Join hooks, structured fields rendered as a
multi-line operator-facing block (current, conflicts with, allowed,
minimum, maximum, why, hint, see), and a configValidator that joins
violations via errors.Join. ConfigErrors walks the tree to collect
every *ConfigError, and Cause keeps existing sentinel identities
reachable through errors.Is.

Updates #3227
2026-09-23 15:18:34 +02:00
Kristoffer Dalby c604874dba policy/v2: suggest approved exit nodes by default
Matches SaaS; Apple clients hide the exit-node list without a suggestion.

Fixes #3415
2026-09-23 14:48:45 +02:00
Kristoffer Dalby 06fa3075da hscontrol: replace tailscale line refs with doc links
Line numbers drift on every upstream bump.
2026-09-23 14:48:45 +02:00
Kristoffer Dalby 0e8a3bba54 policy/v2: compare peer CapMap against SaaS route captures
SaaS stamps suggest-exit-node on approved exit peers without nodeAttrs.

Updates #3415
2026-09-23 14:48:45 +02:00
Kristoffer Dalby e48bc46cc6 mapper: take peer visibility from the peer map only
Fixes #3408
2026-09-23 14:48:35 +02:00
Kristoffer Dalby 5861005ef6 policy/v2: add via exit node capture
Updates #3408
2026-09-23 14:48:35 +02:00
Kristoffer Dalby 23f7eedac6 servertest: compare user-owned nodes in via compat tests
Updates #3408
2026-09-23 14:48:35 +02:00
Kristoffer Dalby 5401edb6a8 policy: ignore '#' metadata fields across the whole policy
The filter lived in ACL.UnmarshalJSON, so grants, ssh and nodeAttrs still
hit RejectUnknownMembers. Strip the members in the HuJSON AST instead, at
the single decode entrypoint. Grant "app" payloads are left untouched.

Fixes #3479
2026-09-23 09:30:22 +02:00