Просмотр исходного кода

cmd/cigocacher,go.mod: add cigocacher cmd

Adds cmd/cigocacher as the client to cigocached for Go caching over
HTTP. The HTTP cache is best-effort only, and builds will fall back to
disk-only cache if it's not available, much like regular builds.

Not yet used in CI; that will follow in another PR once we have runners
available in this repo with the right network setup for reaching
cigocached.

Updates tailscale/corp#10808

Change-Id: I13ae1a12450eb2a05bd9843f358474243989e967
Signed-off-by: Tom Proctor <[email protected]>
Tom Proctor 3 месяцев назад
Родитель
Сommit
6637003cc8

+ 308 - 0
cmd/cigocacher/cigocacher.go

@@ -0,0 +1,308 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// cigocacher is an opinionated-to-Tailscale client for gocached. It connects
+// at a URL like "https://ci-gocached-azure-1.corp.ts.net:31364", but that is
+// stored in a GitHub actions variable so that its hostname can be updated for
+// all branches at the same time in sync with the actual infrastructure.
+//
+// It authenticates using GitHub OIDC tokens, and all HTTP errors are ignored
+// so that its failure mode is just that builds get slower and fall back to
+// disk-only cache.
+package main
+
+import (
+	"bytes"
+	"context"
+	jsonv1 "encoding/json"
+	"errors"
+	"flag"
+	"fmt"
+	"io"
+	"log"
+	"net"
+	"net/http"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync/atomic"
+	"time"
+
+	"github.com/bradfitz/go-tool-cache/cacheproc"
+	"github.com/bradfitz/go-tool-cache/cachers"
+)
+
+func main() {
+	var (
+		auth          = flag.Bool("auth", false, "auth with cigocached and exit, printing the access token as output")
+		token         = flag.String("token", "", "the cigocached access token to use, as created using --auth")
+		cigocachedURL = flag.String("cigocached-url", "", "optional cigocached URL (scheme, host, and port). empty means to not use one.")
+		verbose       = flag.Bool("verbose", false, "enable verbose logging")
+	)
+	flag.Parse()
+
+	if *auth {
+		if *cigocachedURL == "" {
+			log.Print("--cigocached-url is empty, skipping auth")
+			return
+		}
+		tk, err := fetchAccessToken(httpClient(), os.Getenv("ACTIONS_ID_TOKEN_REQUEST_URL"), os.Getenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN"), *cigocachedURL)
+		if err != nil {
+			log.Printf("error fetching access token, skipping auth: %v", err)
+			return
+		}
+		fmt.Println(tk)
+		return
+	}
+
+	d, err := os.UserCacheDir()
+	if err != nil {
+		log.Fatal(err)
+	}
+	d = filepath.Join(d, "go-cacher")
+	log.Printf("Defaulting to cache dir %v ...", d)
+	if err := os.MkdirAll(d, 0750); err != nil {
+		log.Fatal(err)
+	}
+
+	c := &cigocacher{
+		disk:    &cachers.DiskCache{Dir: d},
+		verbose: *verbose,
+	}
+	if *cigocachedURL != "" {
+		log.Printf("Using cigocached at %s", *cigocachedURL)
+		c.gocached = &gocachedClient{
+			baseURL:     *cigocachedURL,
+			cl:          httpClient(),
+			accessToken: *token,
+			verbose:     *verbose,
+		}
+	}
+	var p *cacheproc.Process
+	p = &cacheproc.Process{
+		Close: func() error {
+			log.Printf("gocacheprog: closing; %d gets (%d hits, %d misses, %d errors); %d puts (%d errors)",
+				p.Gets.Load(), p.GetHits.Load(), p.GetMisses.Load(), p.GetErrors.Load(), p.Puts.Load(), p.PutErrors.Load())
+			return c.close()
+		},
+		Get: c.get,
+		Put: c.put,
+	}
+
+	if err := p.Run(); err != nil {
+		log.Fatal(err)
+	}
+}
+
+func httpClient() *http.Client {
+	return &http.Client{
+		Transport: &http.Transport{
+			DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
+				host, port, err := net.SplitHostPort(addr)
+				if err == nil {
+					// This does not run in a tailnet. We serve corp.ts.net
+					// TLS certs, and override DNS resolution to lookup the
+					// private IP for the VM by its hostname.
+					if vm, ok := strings.CutSuffix(host, ".corp.ts.net"); ok {
+						addr = net.JoinHostPort(vm, port)
+					}
+				}
+				var d net.Dialer
+				return d.DialContext(ctx, network, addr)
+			},
+		},
+	}
+}
+
+type cigocacher struct {
+	disk     *cachers.DiskCache
+	gocached *gocachedClient
+	verbose  bool
+
+	getNanos      atomic.Int64 // total nanoseconds spent in gets
+	putNanos      atomic.Int64 // total nanoseconds spent in puts
+	getHTTP       atomic.Int64 // HTTP get requests made
+	getHTTPBytes  atomic.Int64 // HTTP get bytes transferred
+	getHTTPHits   atomic.Int64 // HTTP get hits
+	getHTTPMisses atomic.Int64 // HTTP get misses
+	getHTTPErrors atomic.Int64 // HTTP get errors ignored on best-effort basis
+	getHTTPNanos  atomic.Int64 // total nanoseconds spent in HTTP gets
+	putHTTP       atomic.Int64 // HTTP put requests made
+	putHTTPBytes  atomic.Int64 // HTTP put bytes transferred
+	putHTTPErrors atomic.Int64 // HTTP put errors ignored on best-effort basis
+	putHTTPNanos  atomic.Int64 // total nanoseconds spent in HTTP puts
+}
+
+func (c *cigocacher) get(ctx context.Context, actionID string) (outputID, diskPath string, err error) {
+	t0 := time.Now()
+	defer func() {
+		c.getNanos.Add(time.Since(t0).Nanoseconds())
+	}()
+	if c.gocached == nil {
+		return c.disk.Get(ctx, actionID)
+	}
+
+	outputID, diskPath, err = c.disk.Get(ctx, actionID)
+	if err == nil && outputID != "" {
+		return outputID, diskPath, nil
+	}
+
+	c.getHTTP.Add(1)
+	t0HTTP := time.Now()
+	defer func() {
+		c.getHTTPNanos.Add(time.Since(t0HTTP).Nanoseconds())
+	}()
+	outputID, res, err := c.gocached.get(ctx, actionID)
+	if err != nil {
+		c.getHTTPErrors.Add(1)
+		return "", "", nil
+	}
+	if outputID == "" || res == nil {
+		c.getHTTPMisses.Add(1)
+		return "", "", nil
+	}
+
+	defer res.Body.Close()
+
+	// TODO(tomhjp): make sure we timeout if cigocached disappears, but for some
+	// reason, this seemed to tank network performance.
+	// ctx, cancel := context.WithTimeout(ctx, httpTimeout(res.ContentLength))
+	// defer cancel()
+	diskPath, err = c.disk.Put(ctx, actionID, outputID, res.ContentLength, res.Body)
+	if err != nil {
+		return "", "", fmt.Errorf("error filling disk cache from HTTP: %w", err)
+	}
+
+	c.getHTTPHits.Add(1)
+	c.getHTTPBytes.Add(res.ContentLength)
+	return outputID, diskPath, nil
+}
+
+func (c *cigocacher) put(ctx context.Context, actionID, outputID string, size int64, r io.Reader) (diskPath string, err error) {
+	t0 := time.Now()
+	defer func() {
+		c.putNanos.Add(time.Since(t0).Nanoseconds())
+	}()
+	if c.gocached == nil {
+		return c.disk.Put(ctx, actionID, outputID, size, r)
+	}
+
+	c.putHTTP.Add(1)
+	var diskReader, httpReader io.Reader
+	tee := &bestEffortTeeReader{r: r}
+	if size == 0 {
+		// Special case the empty file so NewRequest sets "Content-Length: 0",
+		// as opposed to thinking we didn't set it and not being able to sniff its size
+		// from the type.
+		diskReader, httpReader = bytes.NewReader(nil), bytes.NewReader(nil)
+	} else {
+		pr, pw := io.Pipe()
+		defer pw.Close()
+		// The diskReader is in the driving seat. We will try to forward data
+		// to httpReader as well, but only best-effort.
+		diskReader = tee
+		tee.w = pw
+		httpReader = pr
+	}
+	httpErrCh := make(chan error)
+	go func() {
+		// TODO(tomhjp): make sure we timeout if cigocached disappears, but for some
+		// reason, this seemed to tank network performance.
+		// ctx, cancel := context.WithTimeout(ctx, httpTimeout(size))
+		// defer cancel()
+		t0HTTP := time.Now()
+		defer func() {
+			c.putHTTPNanos.Add(time.Since(t0HTTP).Nanoseconds())
+		}()
+		httpErrCh <- c.gocached.put(ctx, actionID, outputID, size, httpReader)
+	}()
+
+	diskPath, err = c.disk.Put(ctx, actionID, outputID, size, diskReader)
+	if err != nil {
+		return "", fmt.Errorf("error writing to disk cache: %w", errors.Join(err, tee.err))
+	}
+
+	select {
+	case err := <-httpErrCh:
+		if err != nil {
+			c.putHTTPErrors.Add(1)
+		} else {
+			c.putHTTPBytes.Add(size)
+		}
+	case <-ctx.Done():
+	}
+
+	return diskPath, nil
+}
+
+func (c *cigocacher) close() error {
+	log.Printf("cigocacher HTTP stats: %d gets (%.1fMiB, %.2fs, %d hits, %d misses, %d errors ignored); %d puts (%.1fMiB, %.2fs, %d errors ignored)",
+		c.getHTTP.Load(), float64(c.getHTTPBytes.Load())/float64(1<<20), float64(c.getHTTPNanos.Load())/float64(time.Second), c.getHTTPHits.Load(), c.getHTTPMisses.Load(), c.getHTTPErrors.Load(),
+		c.putHTTP.Load(), float64(c.putHTTPBytes.Load())/float64(1<<20), float64(c.putHTTPNanos.Load())/float64(time.Second), c.putHTTPErrors.Load())
+	if !c.verbose || c.gocached == nil {
+		return nil
+	}
+
+	stats, err := c.gocached.fetchStats()
+	if err != nil {
+		log.Printf("error fetching gocached stats: %v", err)
+	} else {
+		log.Printf("gocached session stats: %s", stats)
+	}
+
+	return nil
+}
+
+func fetchAccessToken(cl *http.Client, idTokenURL, idTokenRequestToken, gocachedURL string) (string, error) {
+	req, err := http.NewRequest("GET", idTokenURL+"&audience=gocached", nil)
+	if err != nil {
+		return "", err
+	}
+	req.Header.Set("Authorization", "Bearer "+idTokenRequestToken)
+	resp, err := cl.Do(req)
+	if err != nil {
+		return "", err
+	}
+	defer resp.Body.Close()
+	type idTokenResp struct {
+		Value string `json:"value"`
+	}
+	var idToken idTokenResp
+	if err := jsonv1.NewDecoder(resp.Body).Decode(&idToken); err != nil {
+		return "", err
+	}
+
+	req, _ = http.NewRequest("POST", gocachedURL+"/auth/exchange-token", strings.NewReader(`{"jwt":"`+idToken.Value+`"}`))
+	req.Header.Set("Content-Type", "application/json")
+	resp, err = cl.Do(req)
+	if err != nil {
+		return "", err
+	}
+	defer resp.Body.Close()
+	type accessTokenResp struct {
+		AccessToken string `json:"access_token"`
+	}
+	var accessToken accessTokenResp
+	if err := jsonv1.NewDecoder(resp.Body).Decode(&accessToken); err != nil {
+		return "", err
+	}
+
+	return accessToken.AccessToken, nil
+}
+
+type bestEffortTeeReader struct {
+	r   io.Reader
+	w   io.WriteCloser
+	err error
+}
+
+func (t *bestEffortTeeReader) Read(p []byte) (int, error) {
+	n, err := t.r.Read(p)
+	if n > 0 && t.w != nil {
+		if _, err := t.w.Write(p[:n]); err != nil {
+			t.err = errors.Join(err, t.w.Close())
+			t.w = nil
+		}
+	}
+	return n, err
+}

+ 115 - 0
cmd/cigocacher/http.go

@@ -0,0 +1,115 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package main
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"log"
+	"net/http"
+)
+
+type gocachedClient struct {
+	baseURL     string       // base URL of the cacher server, like "http://localhost:31364".
+	cl          *http.Client // http.Client to use.
+	accessToken string       // Bearer token to use in the Authorization header.
+	verbose     bool
+}
+
+// drainAndClose reads and throws away a small bounded amount of data. This is a
+// best-effort attempt to allow connection reuse; Go's HTTP/1 Transport won't
+// reuse a TCP connection unless you fully consume HTTP responses.
+func drainAndClose(body io.ReadCloser) {
+	io.CopyN(io.Discard, body, 4<<10)
+	body.Close()
+}
+
+func tryReadErrorMessage(res *http.Response) []byte {
+	msg, _ := io.ReadAll(io.LimitReader(res.Body, 4<<10))
+	return msg
+}
+
+func (c *gocachedClient) get(ctx context.Context, actionID string) (outputID string, resp *http.Response, err error) {
+	// TODO(tomhjp): make sure we timeout if cigocached disappears, but for some
+	// reason, this seemed to tank network performance.
+	// // Set a generous upper limit on the time we'll wait for a response. We'll
+	// // shorten this deadline later once we know the content length.
+	// ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	// defer cancel()
+	req, _ := http.NewRequestWithContext(ctx, "GET", c.baseURL+"/action/"+actionID, nil)
+	req.Header.Set("Want-Object", "1") // opt in to single roundtrip protocol
+	if c.accessToken != "" {
+		req.Header.Set("Authorization", "Bearer "+c.accessToken)
+	}
+
+	res, err := c.cl.Do(req)
+	if err != nil {
+		return "", nil, err
+	}
+	defer func() {
+		if resp == nil {
+			drainAndClose(res.Body)
+		}
+	}()
+	if res.StatusCode == http.StatusNotFound {
+		return "", nil, nil
+	}
+	if res.StatusCode != http.StatusOK {
+		msg := tryReadErrorMessage(res)
+		if c.verbose {
+			log.Printf("error GET /action/%s: %v, %s", actionID, res.Status, msg)
+		}
+		return "", nil, fmt.Errorf("unexpected GET /action/%s status %v", actionID, res.Status)
+	}
+
+	outputID = res.Header.Get("Go-Output-Id")
+	if outputID == "" {
+		return "", nil, fmt.Errorf("missing Go-Output-Id header in response")
+	}
+	if res.ContentLength == -1 {
+		return "", nil, fmt.Errorf("no Content-Length from server")
+	}
+	return outputID, res, nil
+}
+
+func (c *gocachedClient) put(ctx context.Context, actionID, outputID string, size int64, body io.Reader) error {
+	req, _ := http.NewRequestWithContext(ctx, "PUT", c.baseURL+"/"+actionID+"/"+outputID, body)
+	req.ContentLength = size
+	if c.accessToken != "" {
+		req.Header.Set("Authorization", "Bearer "+c.accessToken)
+	}
+	res, err := c.cl.Do(req)
+	if err != nil {
+		if c.verbose {
+			log.Printf("error PUT /%s/%s: %v", actionID, outputID, err)
+		}
+		return err
+	}
+	defer res.Body.Close()
+	if res.StatusCode != http.StatusNoContent {
+		msg := tryReadErrorMessage(res)
+		if c.verbose {
+			log.Printf("error PUT /%s/%s: %v, %s", actionID, outputID, res.Status, msg)
+		}
+		return fmt.Errorf("unexpected PUT /%s/%s status %v", actionID, outputID, res.Status)
+	}
+
+	return nil
+}
+
+func (c *gocachedClient) fetchStats() (string, error) {
+	req, _ := http.NewRequest("GET", c.baseURL+"/session/stats", nil)
+	req.Header.Set("Authorization", "Bearer "+c.accessToken)
+	resp, err := c.cl.Do(req)
+	if err != nil {
+		return "", err
+	}
+	defer resp.Body.Close()
+	b, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return "", err
+	}
+	return string(b), nil
+}

+ 4 - 4
cmd/derper/depaware.txt

@@ -30,9 +30,9 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
         github.com/prometheus/client_model/go                        from github.com/prometheus/client_golang/prometheus+
         github.com/prometheus/common/expfmt                          from github.com/prometheus/client_golang/prometheus+
         github.com/prometheus/common/model                           from github.com/prometheus/client_golang/prometheus+
-  LD    github.com/prometheus/procfs                                 from github.com/prometheus/client_golang/prometheus
-  LD    github.com/prometheus/procfs/internal/fs                     from github.com/prometheus/procfs
-  LD    github.com/prometheus/procfs/internal/util                   from github.com/prometheus/procfs
+   L    github.com/prometheus/procfs                                 from github.com/prometheus/client_golang/prometheus
+   L    github.com/prometheus/procfs/internal/fs                     from github.com/prometheus/procfs
+   L    github.com/prometheus/procfs/internal/util                   from github.com/prometheus/procfs
    W 💣 github.com/tailscale/go-winio                                from tailscale.com/safesocket
    W 💣 github.com/tailscale/go-winio/internal/fs                    from github.com/tailscale/go-winio
    W 💣 github.com/tailscale/go-winio/internal/socket                from github.com/tailscale/go-winio
@@ -72,7 +72,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
         google.golang.org/protobuf/reflect/protoregistry             from google.golang.org/protobuf/encoding/prototext+
         google.golang.org/protobuf/runtime/protoiface                from google.golang.org/protobuf/internal/impl+
         google.golang.org/protobuf/runtime/protoimpl                 from github.com/prometheus/client_model/go+
-        google.golang.org/protobuf/types/known/timestamppb           from github.com/prometheus/client_golang/prometheus+
+     💣 google.golang.org/protobuf/types/known/timestamppb           from github.com/prometheus/client_golang/prometheus+
         tailscale.com                                                from tailscale.com/version
      💣 tailscale.com/atomicfile                                     from tailscale.com/cmd/derper+
         tailscale.com/client/local                                   from tailscale.com/derp/derpserver

+ 7 - 5
cmd/k8s-operator/depaware.txt

@@ -71,8 +71,9 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
         github.com/klauspost/compress/fse                            from github.com/klauspost/compress/huff0
         github.com/klauspost/compress/huff0                          from github.com/klauspost/compress/zstd
         github.com/klauspost/compress/internal/cpuinfo               from github.com/klauspost/compress/huff0+
+     💣 github.com/klauspost/compress/internal/le                    from github.com/klauspost/compress/huff0+
         github.com/klauspost/compress/internal/snapref               from github.com/klauspost/compress/zstd
-        github.com/klauspost/compress/zstd                           from tailscale.com/util/zstdframe+
+        github.com/klauspost/compress/zstd                           from tailscale.com/util/zstdframe
         github.com/klauspost/compress/zstd/internal/xxhash           from github.com/klauspost/compress/zstd
         github.com/mailru/easyjson/buffer                            from github.com/mailru/easyjson/jwriter
      💣 github.com/mailru/easyjson/jlexer                            from github.com/go-openapi/swag
@@ -94,6 +95,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
         github.com/prometheus/client_golang/prometheus/collectors    from sigs.k8s.io/controller-runtime/pkg/internal/controller/metrics+
         github.com/prometheus/client_golang/prometheus/internal      from github.com/prometheus/client_golang/prometheus+
         github.com/prometheus/client_golang/prometheus/promhttp      from sigs.k8s.io/controller-runtime/pkg/metrics/server+
+        github.com/prometheus/client_golang/prometheus/promhttp/internal from github.com/prometheus/client_golang/prometheus/promhttp
         github.com/prometheus/client_model/go                        from github.com/prometheus/client_golang/prometheus+
         github.com/prometheus/common/expfmt                          from github.com/prometheus/client_golang/prometheus+
         github.com/prometheus/common/model                           from github.com/prometheus/client_golang/prometheus+
@@ -180,10 +182,10 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
         google.golang.org/protobuf/reflect/protoregistry             from github.com/golang/protobuf/proto+
         google.golang.org/protobuf/runtime/protoiface                from github.com/golang/protobuf/proto+
         google.golang.org/protobuf/runtime/protoimpl                 from github.com/golang/protobuf/proto+
-        google.golang.org/protobuf/types/descriptorpb                from github.com/google/gnostic-models/openapiv3+
-        google.golang.org/protobuf/types/gofeaturespb                from google.golang.org/protobuf/reflect/protodesc
-        google.golang.org/protobuf/types/known/anypb                 from github.com/google/gnostic-models/compiler+
-        google.golang.org/protobuf/types/known/timestamppb           from github.com/prometheus/client_golang/prometheus+
+     💣 google.golang.org/protobuf/types/descriptorpb                from github.com/google/gnostic-models/openapiv3+
+     💣 google.golang.org/protobuf/types/gofeaturespb                from google.golang.org/protobuf/reflect/protodesc
+     💣 google.golang.org/protobuf/types/known/anypb                 from github.com/google/gnostic-models/compiler+
+     💣 google.golang.org/protobuf/types/known/timestamppb           from github.com/prometheus/client_golang/prometheus+
         gopkg.in/evanphx/json-patch.v4                               from k8s.io/client-go/testing
         gopkg.in/inf.v0                                              from k8s.io/apimachinery/pkg/api/resource
         gopkg.in/yaml.v3                                             from github.com/go-openapi/swag+

+ 4 - 4
cmd/stund/depaware.txt

@@ -14,9 +14,9 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
         github.com/prometheus/client_model/go                        from github.com/prometheus/client_golang/prometheus+
         github.com/prometheus/common/expfmt                          from github.com/prometheus/client_golang/prometheus+
         github.com/prometheus/common/model                           from github.com/prometheus/client_golang/prometheus+
-  LD    github.com/prometheus/procfs                                 from github.com/prometheus/client_golang/prometheus
-  LD    github.com/prometheus/procfs/internal/fs                     from github.com/prometheus/procfs
-  LD    github.com/prometheus/procfs/internal/util                   from github.com/prometheus/procfs
+   L    github.com/prometheus/procfs                                 from github.com/prometheus/client_golang/prometheus
+   L    github.com/prometheus/procfs/internal/fs                     from github.com/prometheus/procfs
+   L    github.com/prometheus/procfs/internal/util                   from github.com/prometheus/procfs
      💣 go4.org/mem                                                  from tailscale.com/metrics+
         go4.org/netipx                                               from tailscale.com/net/tsaddr
         google.golang.org/protobuf/encoding/protodelim               from github.com/prometheus/common/expfmt
@@ -47,7 +47,7 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
         google.golang.org/protobuf/reflect/protoregistry             from google.golang.org/protobuf/encoding/prototext+
         google.golang.org/protobuf/runtime/protoiface                from google.golang.org/protobuf/internal/impl+
         google.golang.org/protobuf/runtime/protoimpl                 from github.com/prometheus/client_model/go+
-        google.golang.org/protobuf/types/known/timestamppb           from github.com/prometheus/client_golang/prometheus+
+     💣 google.golang.org/protobuf/types/known/timestamppb           from github.com/prometheus/client_golang/prometheus+
         tailscale.com                                                from tailscale.com/version
         tailscale.com/envknob                                        from tailscale.com/tsweb+
         tailscale.com/feature                                        from tailscale.com/tsweb

+ 1 - 0
cmd/tailscaled/depaware-min.txt

@@ -16,6 +16,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
         github.com/klauspost/compress/fse                            from github.com/klauspost/compress/huff0
         github.com/klauspost/compress/huff0                          from github.com/klauspost/compress/zstd
         github.com/klauspost/compress/internal/cpuinfo               from github.com/klauspost/compress/huff0+
+     💣 github.com/klauspost/compress/internal/le                    from github.com/klauspost/compress/huff0+
         github.com/klauspost/compress/internal/snapref               from github.com/klauspost/compress/zstd
         github.com/klauspost/compress/zstd                           from tailscale.com/util/zstdframe
         github.com/klauspost/compress/zstd/internal/xxhash           from github.com/klauspost/compress/zstd

+ 1 - 0
cmd/tailscaled/depaware-minbox.txt

@@ -20,6 +20,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
         github.com/klauspost/compress/fse                            from github.com/klauspost/compress/huff0
         github.com/klauspost/compress/huff0                          from github.com/klauspost/compress/zstd
         github.com/klauspost/compress/internal/cpuinfo               from github.com/klauspost/compress/huff0+
+     💣 github.com/klauspost/compress/internal/le                    from github.com/klauspost/compress/huff0+
         github.com/klauspost/compress/internal/snapref               from github.com/klauspost/compress/zstd
         github.com/klauspost/compress/zstd                           from tailscale.com/util/zstdframe
         github.com/klauspost/compress/zstd/internal/xxhash           from github.com/klauspost/compress/zstd

+ 1 - 0
cmd/tailscaled/depaware.txt

@@ -139,6 +139,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
         github.com/klauspost/compress/fse                            from github.com/klauspost/compress/huff0
         github.com/klauspost/compress/huff0                          from github.com/klauspost/compress/zstd
         github.com/klauspost/compress/internal/cpuinfo               from github.com/klauspost/compress/huff0+
+     💣 github.com/klauspost/compress/internal/le                    from github.com/klauspost/compress/huff0+
         github.com/klauspost/compress/internal/snapref               from github.com/klauspost/compress/zstd
         github.com/klauspost/compress/zstd                           from tailscale.com/util/zstdframe
         github.com/klauspost/compress/zstd/internal/xxhash           from github.com/klauspost/compress/zstd

+ 1 - 0
cmd/tsidp/depaware.txt

@@ -36,6 +36,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
         github.com/klauspost/compress/fse                            from github.com/klauspost/compress/huff0
         github.com/klauspost/compress/huff0                          from github.com/klauspost/compress/zstd
         github.com/klauspost/compress/internal/cpuinfo               from github.com/klauspost/compress/huff0+
+     💣 github.com/klauspost/compress/internal/le                    from github.com/klauspost/compress/huff0+
         github.com/klauspost/compress/internal/snapref               from github.com/klauspost/compress/zstd
         github.com/klauspost/compress/zstd                           from tailscale.com/util/zstdframe
         github.com/klauspost/compress/zstd/internal/xxhash           from github.com/klauspost/compress/zstd

+ 1 - 2
flake.nix

@@ -151,5 +151,4 @@
     });
   };
 }
-# nix-direnv cache busting line: sha256-3jAfCtp714acePnwgdNto8Sj3vFwtpO9os6IwXQ07A4=
-
+# nix-direnv cache busting line: sha256-jJSSXMyUqcJoZuqfSlBsKDQezyqS+jDkRglMMjG1K8g=

+ 8 - 7
go.mod

@@ -16,6 +16,7 @@ require (
 	github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.58
 	github.com/aws/aws-sdk-go-v2/service/s3 v1.75.3
 	github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7
+	github.com/bradfitz/go-tool-cache v0.0.0-20251113223507-0124e698e0bd
 	github.com/bramvdbogaerde/go-scp v1.4.0
 	github.com/cilium/ebpf v0.15.0
 	github.com/coder/websocket v1.8.12
@@ -60,7 +61,7 @@ require (
 	github.com/jellydator/ttlcache/v3 v3.1.0
 	github.com/jsimonetti/rtnetlink v1.4.0
 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
-	github.com/klauspost/compress v1.17.11
+	github.com/klauspost/compress v1.18.0
 	github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a
 	github.com/mattn/go-colorable v0.1.13
 	github.com/mattn/go-isatty v0.0.20
@@ -74,8 +75,8 @@ require (
 	github.com/pkg/errors v0.9.1
 	github.com/pkg/sftp v1.13.6
 	github.com/prometheus-community/pro-bing v0.4.0
-	github.com/prometheus/client_golang v1.20.5
-	github.com/prometheus/common v0.55.0
+	github.com/prometheus/client_golang v1.23.0
+	github.com/prometheus/common v0.65.0
 	github.com/prometheus/prometheus v0.49.2-0.20240125131847-c3b8ef1694ff
 	github.com/safchain/ethtool v0.3.0
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
@@ -103,7 +104,7 @@ require (
 	go4.org/mem v0.0.0-20240501181205-ae6ca9944745
 	go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
 	golang.org/x/crypto v0.45.0
-	golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac
+	golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b
 	golang.org/x/mod v0.30.0
 	golang.org/x/net v0.47.0
 	golang.org/x/oauth2 v0.30.0
@@ -355,8 +356,8 @@ require (
 	github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e // indirect
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 	github.com/polyfloyd/go-errorlint v1.4.8 // indirect
-	github.com/prometheus/client_model v0.6.1
-	github.com/prometheus/procfs v0.15.1 // indirect
+	github.com/prometheus/client_model v0.6.2
+	github.com/prometheus/procfs v0.16.1 // indirect
 	github.com/quasilyte/go-ruleguard v0.4.2 // indirect
 	github.com/quasilyte/gogrep v0.5.0 // indirect
 	github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
@@ -414,7 +415,7 @@ require (
 	golang.org/x/image v0.27.0 // indirect
 	golang.org/x/text v0.31.0 // indirect
 	gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
-	google.golang.org/protobuf v1.36.3 // indirect
+	google.golang.org/protobuf v1.36.6 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect

+ 1 - 1
go.mod.sri

@@ -1 +1 @@
-sha256-3jAfCtp714acePnwgdNto8Sj3vFwtpO9os6IwXQ07A4=
+sha256-jJSSXMyUqcJoZuqfSlBsKDQezyqS+jDkRglMMjG1K8g=

+ 16 - 14
go.sum

@@ -186,6 +186,8 @@ github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
 github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
 github.com/bombsimon/wsl/v4 v4.2.1 h1:Cxg6u+XDWff75SIFFmNsqnIOgob+Q9hG6y/ioKbRFiM=
 github.com/bombsimon/wsl/v4 v4.2.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo=
+github.com/bradfitz/go-tool-cache v0.0.0-20251113223507-0124e698e0bd h1:1Df3FBmfyUCIQ4eKzAPXIWTfewY89L0fWPWO56zWCyI=
+github.com/bradfitz/go-tool-cache v0.0.0-20251113223507-0124e698e0bd/go.mod h1:2+xptBAd0m2kZ1wLO4AYZhldLEFPy+KeGwmnlXLvy+w=
 github.com/bramvdbogaerde/go-scp v1.4.0 h1:jKMwpwCbcX1KyvDbm/PDJuXcMuNVlLGi0Q0reuzjyKY=
 github.com/bramvdbogaerde/go-scp v1.4.0/go.mod h1:on2aH5AxaFb2G0N5Vsdy6B0Ml7k9HuHSwfo1y0QzAbQ=
 github.com/breml/bidichk v0.2.7 h1:dAkKQPLl/Qrk7hnP6P+E0xOodrq8Us7+U0o4UBOAlQY=
@@ -662,8 +664,8 @@ github.com/kisielk/errcheck v1.7.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/kkHAIKE/contextcheck v1.1.4 h1:B6zAaLhOEEcjvUgIYEqystmnFk1Oemn8bvJhbt0GMb8=
 github.com/kkHAIKE/contextcheck v1.1.4/go.mod h1:1+i/gWqokIa+dm31mqGLZhZJ7Uh44DJGZVmr6QRBNJg=
-github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
-github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
+github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
+github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
 github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
 github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -840,29 +842,29 @@ github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP
 github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
 github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
 github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
-github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
-github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
+github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
+github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
-github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
+github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
+github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
 github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
-github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
-github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
+github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
+github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
-github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
+github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
+github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
 github.com/prometheus/prometheus v0.49.2-0.20240125131847-c3b8ef1694ff h1:X1Tly81aZ22DA1fxBdfvR3iw8+yFoUBUHMEd+AX/ZXI=
 github.com/prometheus/prometheus v0.49.2-0.20240125131847-c3b8ef1694ff/go.mod h1:FvE8dtQ1Ww63IlyKBn1V4s+zMwF9kHkVNkQBR1pM4CU=
 github.com/puzpuzpuz/xsync v1.5.2 h1:yRAP4wqSOZG+/4pxJ08fPTwrfL0IzE/LKQ/cw509qGY=
@@ -1140,8 +1142,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs=
-golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo=
+golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
+golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
 golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
 golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
 golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8=
@@ -1498,8 +1500,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
-google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
+google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

+ 1 - 1
shell.nix

@@ -16,4 +16,4 @@
 ) {
   src =  ./.;
 }).shellNix
-# nix-direnv cache busting line: sha256-3jAfCtp714acePnwgdNto8Sj3vFwtpO9os6IwXQ07A4=
+# nix-direnv cache busting line: sha256-jJSSXMyUqcJoZuqfSlBsKDQezyqS+jDkRglMMjG1K8g=

+ 1 - 0
tsnet/depaware.txt

@@ -36,6 +36,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
         github.com/klauspost/compress/fse                            from github.com/klauspost/compress/huff0
         github.com/klauspost/compress/huff0                          from github.com/klauspost/compress/zstd
         github.com/klauspost/compress/internal/cpuinfo               from github.com/klauspost/compress/huff0+
+     💣 github.com/klauspost/compress/internal/le                    from github.com/klauspost/compress/huff0+
         github.com/klauspost/compress/internal/snapref               from github.com/klauspost/compress/zstd
         github.com/klauspost/compress/zstd                           from tailscale.com/util/zstdframe
         github.com/klauspost/compress/zstd/internal/xxhash           from github.com/klauspost/compress/zstd