feature/clientupdate: move clientupdate to a modular feature, disabled for tsnet

Updates #12614

Change-Id: I5f685dec84a5396b7c2b66f2788ae3d286e1ddc6
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2025-09-26 16:41:26 -07:00 committed by Brad Fitzpatrick
parent 69c79cb9f3
commit 038cdb4640
24 changed files with 749 additions and 609 deletions

View File

@ -24,9 +24,9 @@ import (
"tailscale.com/client/local"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/clientupdate"
"tailscale.com/envknob"
"tailscale.com/envknob/featureknob"
"tailscale.com/feature"
"tailscale.com/hostinfo"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
@ -1049,7 +1049,7 @@ func availableFeatures() map[string]bool {
"advertise-routes": true, // available on all platforms
"use-exit-node": featureknob.CanUseExitNode() == nil,
"ssh": featureknob.CanRunTailscaleSSH() == nil,
"auto-update": version.IsUnstableBuild() && clientupdate.CanAutoUpdate(),
"auto-update": version.IsUnstableBuild() && feature.CanAutoUpdate(),
}
return features
}

View File

@ -27,6 +27,7 @@ import (
"strconv"
"strings"
"tailscale.com/feature"
"tailscale.com/hostinfo"
"tailscale.com/types/lazy"
"tailscale.com/types/logger"
@ -252,9 +253,13 @@ func (up *Updater) getUpdateFunction() (fn updateFunction, canAutoUpdate bool) {
var canAutoUpdateCache lazy.SyncValue[bool]
// CanAutoUpdate reports whether auto-updating via the clientupdate package
func init() {
feature.HookCanAutoUpdate.Set(canAutoUpdate)
}
// canAutoUpdate reports whether auto-updating via the clientupdate package
// is supported for the current os/distro.
func CanAutoUpdate() bool { return canAutoUpdateCache.Get(canAutoUpdateUncached) }
func canAutoUpdate() bool { return canAutoUpdateCache.Get(canAutoUpdateUncached) }
func canAutoUpdateUncached() bool {
if version.IsMacSysExt() {

View File

@ -12,7 +12,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
github.com/coder/websocket/internal/util from github.com/coder/websocket
github.com/coder/websocket/internal/xsync from github.com/coder/websocket
💣 github.com/davecgh/go-spew/spew from k8s.io/apimachinery/pkg/util/dump
W 💣 github.com/dblohm7/wingoes from github.com/dblohm7/wingoes/com+
W 💣 github.com/dblohm7/wingoes from tailscale.com/net/tshttpproxy+
W 💣 github.com/dblohm7/wingoes/com from tailscale.com/util/osdiag+
W 💣 github.com/dblohm7/wingoes/com/automation from tailscale.com/util/osdiag/internal/wsc
W github.com/dblohm7/wingoes/internal from github.com/dblohm7/wingoes/com
@ -60,7 +60,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
github.com/google/gofuzz from k8s.io/apimachinery/pkg/apis/meta/v1+
github.com/google/gofuzz/bytesource from github.com/google/gofuzz
github.com/google/uuid from github.com/prometheus-community/pro-bing+
github.com/hdevalence/ed25519consensus from tailscale.com/clientupdate/distsign+
github.com/hdevalence/ed25519consensus from tailscale.com/tka
github.com/josharian/intern from github.com/mailru/easyjson/jlexer
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/netmon
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
@ -686,8 +686,6 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
tailscale.com/client/tailscale from tailscale.com/cmd/k8s-operator+
tailscale.com/client/tailscale/apitype from tailscale.com/client/tailscale+
tailscale.com/client/web from tailscale.com/ipn/ipnlocal
tailscale.com/clientupdate from tailscale.com/client/web+
LW tailscale.com/clientupdate/distsign from tailscale.com/clientupdate
tailscale.com/control/controlbase from tailscale.com/control/controlhttp+
tailscale.com/control/controlclient from tailscale.com/ipn/ipnlocal+
tailscale.com/control/controlhttp from tailscale.com/control/controlclient
@ -780,7 +778,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
tailscale.com/net/tlsdial/blockblame from tailscale.com/net/tlsdial
tailscale.com/net/tsaddr from tailscale.com/client/web+
tailscale.com/net/tsdial from tailscale.com/control/controlclient+
💣 tailscale.com/net/tshttpproxy from tailscale.com/clientupdate/distsign+
💣 tailscale.com/net/tshttpproxy from tailscale.com/control/controlclient+
tailscale.com/net/tstun from tailscale.com/tsd+
tailscale.com/net/udprelay/endpoint from tailscale.com/wgengine/magicsock
tailscale.com/net/udprelay/status from tailscale.com/client/local
@ -829,7 +827,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
tailscale.com/util/cibuild from tailscale.com/health
tailscale.com/util/clientmetric from tailscale.com/cmd/k8s-operator+
tailscale.com/util/cloudenv from tailscale.com/hostinfo+
tailscale.com/util/cmpver from tailscale.com/clientupdate+
LW tailscale.com/util/cmpver from tailscale.com/net/dns+
tailscale.com/util/ctxkey from tailscale.com/client/tailscale/apitype+
💣 tailscale.com/util/deephash from tailscale.com/ipn/ipnlocal+
L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics
@ -843,7 +841,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
tailscale.com/util/lineiter from tailscale.com/hostinfo+
tailscale.com/util/mak from tailscale.com/appc+
tailscale.com/util/multierr from tailscale.com/control/controlclient+
tailscale.com/util/must from tailscale.com/clientupdate/distsign+
tailscale.com/util/must from tailscale.com/logpolicy+
tailscale.com/util/nocasemaps from tailscale.com/types/ipproto
💣 tailscale.com/util/osdiag from tailscale.com/ipn/localapi
W 💣 tailscale.com/util/osdiag/internal/wsc from tailscale.com/util/osdiag
@ -869,8 +867,8 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
tailscale.com/util/truncate from tailscale.com/logtail
tailscale.com/util/usermetric from tailscale.com/health+
tailscale.com/util/vizerror from tailscale.com/tailcfg+
💣 tailscale.com/util/winutil from tailscale.com/clientupdate+
W 💣 tailscale.com/util/winutil/authenticode from tailscale.com/clientupdate+
💣 tailscale.com/util/winutil from tailscale.com/hostinfo+
W 💣 tailscale.com/util/winutil/authenticode from tailscale.com/util/osdiag
W 💣 tailscale.com/util/winutil/gp from tailscale.com/net/dns+
W tailscale.com/util/winutil/policy from tailscale.com/ipn/ipnlocal
W 💣 tailscale.com/util/winutil/winenv from tailscale.com/hostinfo+
@ -907,7 +905,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
LD golang.org/x/crypto/ssh from tailscale.com/ipn/ipnlocal
LD golang.org/x/crypto/ssh/internal/bcrypt_pbkdf from golang.org/x/crypto/ssh
golang.org/x/exp/constraints from github.com/dblohm7/wingoes/pe+
golang.org/x/exp/constraints from tailscale.com/tsweb/varz+
golang.org/x/exp/maps from sigs.k8s.io/controller-runtime/pkg/cache+
golang.org/x/exp/slices from tailscale.com/cmd/k8s-operator+
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
@ -944,13 +942,12 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
golang.org/x/text/unicode/bidi from golang.org/x/net/idna+
golang.org/x/text/unicode/norm from golang.org/x/net/idna
golang.org/x/time/rate from gvisor.dev/gvisor/pkg/log+
archive/tar from tailscale.com/clientupdate
bufio from compress/flate+
bytes from archive/tar+
bytes from bufio+
cmp from github.com/gaissmai/bart+
compress/flate from compress/gzip+
compress/gzip from github.com/emicklei/go-restful/v3+
compress/zlib from debug/pe+
compress/zlib from github.com/emicklei/go-restful/v3+
container/heap from gvisor.dev/gvisor/pkg/tcpip/transport/tcp+
container/list from crypto/tls+
context from crypto/tls+
@ -1034,10 +1031,10 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
encoding/json from expvar+
encoding/pem from crypto/tls+
encoding/xml from github.com/emicklei/go-restful/v3+
errors from archive/tar+
errors from bufio+
expvar from github.com/prometheus/client_golang/prometheus+
flag from github.com/spf13/pflag+
fmt from archive/tar+
fmt from compress/flate+
go/ast from go/doc+
go/build/constraint from go/parser
go/doc from k8s.io/apimachinery/pkg/runtime
@ -1063,7 +1060,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
internal/filepathlite from os+
internal/fmtsort from fmt+
internal/goarch from crypto/internal/fips140deps/cpu+
internal/godebug from archive/tar+
internal/godebug from crypto/internal/fips140deps/godebug+
internal/godebugs from internal/godebug+
internal/goexperiment from hash/maphash+
internal/goos from crypto/x509+
@ -1100,8 +1097,8 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
internal/testlog from os
internal/trace/tracev2 from runtime+
internal/unsafeheader from internal/reflectlite+
io from archive/tar+
io/fs from archive/tar+
io from bufio+
io/fs from crypto/x509+
io/ioutil from github.com/digitalocean/go-smbios/smbios+
iter from go/ast+
log from expvar+
@ -1110,7 +1107,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
log/slog/internal from log/slog
log/slog/internal/buffer from log/slog
maps from sigs.k8s.io/controller-runtime/pkg/predicate+
math from archive/tar+
math from compress/flate+
math/big from crypto/dsa+
math/bits from compress/flate+
math/rand from github.com/google/go-cmp/cmp+
@ -1132,29 +1129,29 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
os from crypto/internal/sysrand+
os/exec from github.com/godbus/dbus/v5+
os/signal from sigs.k8s.io/controller-runtime/pkg/manager/signals
os/user from archive/tar+
path from archive/tar+
path/filepath from archive/tar+
reflect from archive/tar+
os/user from github.com/godbus/dbus/v5+
path from debug/dwarf+
path/filepath from crypto/x509+
reflect from crypto/x509+
regexp from github.com/davecgh/go-spew/spew+
regexp/syntax from regexp
runtime from archive/tar+
runtime from crypto/internal/fips140+
runtime/debug from github.com/coder/websocket/internal/xsync+
runtime/metrics from github.com/prometheus/client_golang/prometheus+
runtime/pprof from net/http/pprof+
runtime/trace from net/http/pprof
slices from encoding/base32+
sort from compress/flate+
strconv from archive/tar+
strings from archive/tar+
strconv from compress/flate+
strings from bufio+
W structs from internal/syscall/windows
sync from archive/tar+
sync from compress/flate+
sync/atomic from context+
syscall from archive/tar+
syscall from crypto/internal/sysrand+
text/tabwriter from k8s.io/apimachinery/pkg/util/diff+
text/template from html/template
text/template/parse from html/template+
time from archive/tar+
time from compress/gzip+
unicode from bytes+
unicode/utf16 from crypto/x509+
unicode/utf8 from bufio+

View File

@ -15,8 +15,8 @@ import (
"strings"
"github.com/peterbourgon/ff/v3/ffcli"
"tailscale.com/clientupdate"
"tailscale.com/cmd/tailscale/cli/ffcomplete"
"tailscale.com/feature/buildfeatures"
"tailscale.com/ipn"
"tailscale.com/net/netutil"
"tailscale.com/net/tsaddr"
@ -226,21 +226,14 @@ func runSet(ctx context.Context, args []string) (retErr error) {
return err
}
}
if maskedPrefs.AutoUpdateSet.ApplySet {
if !clientupdate.CanAutoUpdate() {
return errors.New("automatic updates are not supported on this platform")
if maskedPrefs.AutoUpdateSet.ApplySet && buildfeatures.HasClientUpdate && version.IsMacSysExt() {
apply := "0"
if maskedPrefs.AutoUpdate.Apply.EqualBool(true) {
apply = "1"
}
// On macsys, tailscaled will set the Sparkle auto-update setting. It
// does not use clientupdate.
if version.IsMacSysExt() {
apply := "0"
if maskedPrefs.AutoUpdate.Apply.EqualBool(true) {
apply = "1"
}
out, err := exec.Command("defaults", "write", "io.tailscale.ipn.macsys", "SUAutomaticallyUpdate", apply).CombinedOutput()
if err != nil {
return fmt.Errorf("failed to enable automatic updates: %v, %q", err, out)
}
out, err := exec.Command("defaults", "write", "io.tailscale.ipn.macsys", "SUAutomaticallyUpdate", apply).CombinedOutput()
if err != nil {
return fmt.Errorf("failed to enable automatic updates: %v, %q", err, out)
}
}

View File

@ -77,7 +77,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/client/tailscale from tailscale.com/internal/client/tailscale
tailscale.com/client/tailscale/apitype from tailscale.com/client/tailscale+
tailscale.com/client/web from tailscale.com/cmd/tailscale/cli
tailscale.com/clientupdate from tailscale.com/client/web+
tailscale.com/clientupdate from tailscale.com/cmd/tailscale/cli
LW tailscale.com/clientupdate/distsign from tailscale.com/clientupdate
tailscale.com/cmd/tailscale/cli from tailscale.com/cmd/tailscale
tailscale.com/cmd/tailscale/cli/ffcomplete from tailscale.com/cmd/tailscale/cli

View File

@ -1,7 +1,5 @@
tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/depaware)
filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus
filippo.io/edwards25519/field from filippo.io/edwards25519
github.com/digitalocean/go-smbios/smbios from tailscale.com/posture
github.com/gaissmai/bart from tailscale.com/net/ipset+
github.com/gaissmai/bart/internal/bitset from github.com/gaissmai/bart+
@ -13,7 +11,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
github.com/go-json-experiment/json/internal/jsonwire from github.com/go-json-experiment/json+
github.com/go-json-experiment/json/jsontext from github.com/go-json-experiment/json+
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
github.com/hdevalence/ed25519consensus from tailscale.com/clientupdate/distsign
💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/netmon
github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
github.com/klauspost/compress from github.com/klauspost/compress/zstd
@ -44,8 +41,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/appc from tailscale.com/ipn/ipnlocal+
tailscale.com/atomicfile from tailscale.com/ipn+
tailscale.com/client/tailscale/apitype from tailscale.com/ipn/ipnauth+
tailscale.com/clientupdate from tailscale.com/ipn/ipnlocal+
tailscale.com/clientupdate/distsign from tailscale.com/clientupdate
tailscale.com/cmd/tailscaled/childproc from tailscale.com/cmd/tailscaled
tailscale.com/control/controlbase from tailscale.com/control/controlhttp+
tailscale.com/control/controlclient from tailscale.com/cmd/tailscaled+
@ -65,7 +60,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/feature/condregister/portmapper from tailscale.com/feature/condregister
tailscale.com/health from tailscale.com/cmd/tailscaled+
tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal+
tailscale.com/hostinfo from tailscale.com/clientupdate+
tailscale.com/hostinfo from tailscale.com/cmd/tailscaled+
tailscale.com/internal/noiseconn from tailscale.com/control/controlclient
tailscale.com/ipn from tailscale.com/cmd/tailscaled+
tailscale.com/ipn/conffile from tailscale.com/cmd/tailscaled+
@ -116,7 +111,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/net/tlsdial/blockblame from tailscale.com/net/tlsdial
tailscale.com/net/tsaddr from tailscale.com/ipn+
tailscale.com/net/tsdial from tailscale.com/cmd/tailscaled+
tailscale.com/net/tshttpproxy from tailscale.com/clientupdate/distsign+
tailscale.com/net/tshttpproxy from tailscale.com/cmd/tailscaled+
tailscale.com/net/tstun from tailscale.com/cmd/tailscaled+
tailscale.com/net/udprelay/endpoint from tailscale.com/wgengine/magicsock
tailscale.com/omit from tailscale.com/ipn/conffile
@ -142,7 +137,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/types/flagtype from tailscale.com/cmd/tailscaled
tailscale.com/types/ipproto from tailscale.com/ipn+
tailscale.com/types/key from tailscale.com/cmd/tailscaled+
tailscale.com/types/lazy from tailscale.com/clientupdate+
tailscale.com/types/lazy from tailscale.com/hostinfo+
tailscale.com/types/logger from tailscale.com/appc+
tailscale.com/types/logid from tailscale.com/cmd/tailscaled+
tailscale.com/types/mapx from tailscale.com/ipn/ipnext
@ -161,7 +156,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/util/cibuild from tailscale.com/health
tailscale.com/util/clientmetric from tailscale.com/appc+
tailscale.com/util/cloudenv from tailscale.com/hostinfo+
tailscale.com/util/cmpver from tailscale.com/clientupdate
tailscale.com/util/ctxkey from tailscale.com/client/tailscale/apitype+
💣 tailscale.com/util/deephash from tailscale.com/ipn/ipnlocal+
💣 tailscale.com/util/dirwalk from tailscale.com/metrics
@ -171,11 +165,11 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/util/goroutines from tailscale.com/ipn/ipnlocal
tailscale.com/util/groupmember from tailscale.com/ipn/ipnauth
💣 tailscale.com/util/hashx from tailscale.com/util/deephash
tailscale.com/util/httpm from tailscale.com/clientupdate/distsign+
tailscale.com/util/httpm from tailscale.com/ipn/ipnlocal+
tailscale.com/util/lineiter from tailscale.com/hostinfo+
tailscale.com/util/mak from tailscale.com/appc+
tailscale.com/util/multierr from tailscale.com/cmd/tailscaled+
tailscale.com/util/must from tailscale.com/clientupdate/distsign+
tailscale.com/util/must from tailscale.com/logpolicy+
tailscale.com/util/nocasemaps from tailscale.com/types/ipproto
tailscale.com/util/osdiag from tailscale.com/ipn/localapi
tailscale.com/util/osshare from tailscale.com/cmd/tailscaled
@ -195,8 +189,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/util/vizerror from tailscale.com/tailcfg+
tailscale.com/util/winutil from tailscale.com/ipn/ipnauth
tailscale.com/util/zstdframe from tailscale.com/control/controlclient
tailscale.com/version from tailscale.com/clientupdate+
tailscale.com/version/distro from tailscale.com/clientupdate+
tailscale.com/version from tailscale.com/cmd/tailscaled+
tailscale.com/version/distro from tailscale.com/cmd/tailscaled+
tailscale.com/wgengine from tailscale.com/cmd/tailscaled+
tailscale.com/wgengine/filter from tailscale.com/control/controlclient+
tailscale.com/wgengine/filter/filtertype from tailscale.com/types/netmap+
@ -249,9 +243,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/text/unicode/bidi from golang.org/x/net/idna+
golang.org/x/text/unicode/norm from golang.org/x/net/idna
golang.org/x/time/rate from tailscale.com/derp
archive/tar from tailscale.com/clientupdate
bufio from compress/flate+
bytes from archive/tar+
bytes from bufio+
cmp from encoding/json+
compress/flate from compress/gzip
compress/gzip from golang.org/x/net/http2+
@ -329,10 +322,10 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
encoding/hex from crypto/x509+
encoding/json from expvar+
encoding/pem from crypto/tls+
errors from archive/tar+
errors from bufio+
expvar from tailscale.com/health+
flag from tailscale.com/cmd/tailscaled+
fmt from archive/tar+
fmt from compress/flate+
hash from crypto+
hash/crc32 from compress/gzip+
hash/maphash from go4.org/mem
@ -348,7 +341,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
internal/filepathlite from os+
internal/fmtsort from fmt
internal/goarch from crypto/internal/fips140deps/cpu+
internal/godebug from archive/tar+
internal/godebug from crypto/internal/fips140deps/godebug+
internal/godebugs from internal/godebug+
internal/goexperiment from hash/maphash+
internal/goos from crypto/x509+
@ -379,14 +372,14 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
internal/testlog from os
internal/trace/tracev2 from runtime+
internal/unsafeheader from internal/reflectlite+
io from archive/tar+
io/fs from archive/tar+
io from bufio+
io/fs from crypto/x509+
io/ioutil from github.com/digitalocean/go-smbios/smbios
iter from bytes+
log from expvar+
log/internal from log
maps from archive/tar+
math from archive/tar+
maps from crypto/x509+
math from compress/flate+
math/big from crypto/dsa+
math/bits from bytes+
math/rand from github.com/mdlayher/netlink+
@ -405,27 +398,27 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
net/textproto from golang.org/x/net/http/httpguts+
net/url from crypto/x509+
os from crypto/internal/sysrand+
os/exec from tailscale.com/clientupdate+
os/exec from tailscale.com/hostinfo+
os/signal from tailscale.com/cmd/tailscaled
os/user from archive/tar+
path from archive/tar+
path/filepath from archive/tar+
reflect from archive/tar+
os/user from tailscale.com/ipn/ipnauth+
path from io/fs+
path/filepath from crypto/x509+
reflect from crypto/x509+
regexp from internal/profile+
regexp/syntax from regexp
runtime from archive/tar+
runtime from crypto/internal/fips140+
runtime/debug from github.com/klauspost/compress/zstd+
runtime/pprof from net/http/pprof+
runtime/trace from net/http/pprof
slices from archive/tar+
slices from crypto/tls+
sort from compress/flate+
strconv from archive/tar+
strings from archive/tar+
sync from archive/tar+
strconv from compress/flate+
strings from bufio+
sync from compress/flate+
sync/atomic from context+
syscall from archive/tar+
syscall from crypto/internal/sysrand+
text/tabwriter from runtime/pprof
time from archive/tar+
time from compress/gzip+
unicode from bytes+
unicode/utf16 from crypto/x509+
unicode/utf8 from bufio+

View File

@ -246,7 +246,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/client/local from tailscale.com/client/web+
tailscale.com/client/tailscale/apitype from tailscale.com/client/local+
tailscale.com/client/web from tailscale.com/ipn/ipnlocal
tailscale.com/clientupdate from tailscale.com/client/web+
tailscale.com/clientupdate from tailscale.com/feature/clientupdate
LW tailscale.com/clientupdate/distsign from tailscale.com/clientupdate
tailscale.com/cmd/tailscaled/childproc from tailscale.com/cmd/tailscaled+
tailscale.com/cmd/tailscaled/tailscaledhooks from tailscale.com/cmd/tailscaled+
@ -273,6 +273,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/feature from tailscale.com/feature/wakeonlan+
tailscale.com/feature/buildfeatures from tailscale.com/wgengine/magicsock+
tailscale.com/feature/capture from tailscale.com/feature/condregister
tailscale.com/feature/clientupdate from tailscale.com/feature/condregister
tailscale.com/feature/condregister from tailscale.com/cmd/tailscaled
tailscale.com/feature/condregister/portmapper from tailscale.com/feature/condregister
tailscale.com/feature/debugportmapper from tailscale.com/feature/condregister

View File

@ -9,7 +9,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
github.com/coder/websocket/internal/errd from github.com/coder/websocket
github.com/coder/websocket/internal/util from github.com/coder/websocket
github.com/coder/websocket/internal/xsync from github.com/coder/websocket
W 💣 github.com/dblohm7/wingoes from github.com/dblohm7/wingoes/com+
W 💣 github.com/dblohm7/wingoes from tailscale.com/net/tshttpproxy+
W 💣 github.com/dblohm7/wingoes/com from tailscale.com/util/osdiag+
W 💣 github.com/dblohm7/wingoes/com/automation from tailscale.com/util/osdiag/internal/wsc
W github.com/dblohm7/wingoes/internal from github.com/dblohm7/wingoes/com
@ -28,8 +28,8 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/header+
DW github.com/google/uuid from github.com/prometheus-community/pro-bing+
github.com/hdevalence/ed25519consensus from tailscale.com/clientupdate/distsign+
D github.com/google/uuid from github.com/prometheus-community/pro-bing
github.com/hdevalence/ed25519consensus from tailscale.com/tka
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/netmon
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
github.com/klauspost/compress from github.com/klauspost/compress/zstd
@ -128,8 +128,6 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
tailscale.com/client/tailscale from tailscale.com/internal/client/tailscale
tailscale.com/client/tailscale/apitype from tailscale.com/client/local+
tailscale.com/client/web from tailscale.com/ipn/ipnlocal
tailscale.com/clientupdate from tailscale.com/client/web+
LW tailscale.com/clientupdate/distsign from tailscale.com/clientupdate
tailscale.com/control/controlbase from tailscale.com/control/controlhttp+
tailscale.com/control/controlclient from tailscale.com/ipn/ipnext+
tailscale.com/control/controlhttp from tailscale.com/control/controlclient
@ -208,7 +206,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
tailscale.com/net/tlsdial/blockblame from tailscale.com/net/tlsdial
tailscale.com/net/tsaddr from tailscale.com/client/web+
tailscale.com/net/tsdial from tailscale.com/control/controlclient+
💣 tailscale.com/net/tshttpproxy from tailscale.com/clientupdate/distsign+
💣 tailscale.com/net/tshttpproxy from tailscale.com/control/controlclient+
tailscale.com/net/tstun from tailscale.com/tsd+
tailscale.com/net/udprelay/endpoint from tailscale.com/wgengine/magicsock
tailscale.com/net/udprelay/status from tailscale.com/client/local
@ -237,7 +235,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
tailscale.com/types/empty from tailscale.com/ipn+
tailscale.com/types/ipproto from tailscale.com/ipn+
tailscale.com/types/key from tailscale.com/client/local+
tailscale.com/types/lazy from tailscale.com/clientupdate+
tailscale.com/types/lazy from tailscale.com/cmd/tsidp+
tailscale.com/types/logger from tailscale.com/appc+
tailscale.com/types/logid from tailscale.com/ipn/ipnlocal+
tailscale.com/types/mapx from tailscale.com/ipn/ipnext
@ -256,12 +254,12 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
tailscale.com/util/cibuild from tailscale.com/health
tailscale.com/util/clientmetric from tailscale.com/appc+
tailscale.com/util/cloudenv from tailscale.com/hostinfo+
tailscale.com/util/cmpver from tailscale.com/clientupdate+
LW tailscale.com/util/cmpver from tailscale.com/net/dns+
tailscale.com/util/ctxkey from tailscale.com/client/tailscale/apitype+
💣 tailscale.com/util/deephash from tailscale.com/ipn/ipnlocal+
L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics
tailscale.com/util/dnsname from tailscale.com/appc+
tailscale.com/util/eventbus from tailscale.com/ipn/localapi+
tailscale.com/util/eventbus from tailscale.com/client/local+
tailscale.com/util/execqueue from tailscale.com/appc+
tailscale.com/util/goroutines from tailscale.com/ipn/ipnlocal
tailscale.com/util/groupmember from tailscale.com/client/web+
@ -270,7 +268,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
tailscale.com/util/lineiter from tailscale.com/hostinfo+
tailscale.com/util/mak from tailscale.com/appc+
tailscale.com/util/multierr from tailscale.com/control/controlclient+
tailscale.com/util/must from tailscale.com/clientupdate/distsign+
tailscale.com/util/must from tailscale.com/cmd/tsidp+
tailscale.com/util/nocasemaps from tailscale.com/types/ipproto
💣 tailscale.com/util/osdiag from tailscale.com/ipn/localapi
W 💣 tailscale.com/util/osdiag/internal/wsc from tailscale.com/util/osdiag
@ -296,8 +294,8 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
tailscale.com/util/truncate from tailscale.com/logtail
tailscale.com/util/usermetric from tailscale.com/health+
tailscale.com/util/vizerror from tailscale.com/tailcfg+
💣 tailscale.com/util/winutil from tailscale.com/clientupdate+
W 💣 tailscale.com/util/winutil/authenticode from tailscale.com/clientupdate+
💣 tailscale.com/util/winutil from tailscale.com/hostinfo+
W 💣 tailscale.com/util/winutil/authenticode from tailscale.com/util/osdiag
W 💣 tailscale.com/util/winutil/gp from tailscale.com/net/dns+
W tailscale.com/util/winutil/policy from tailscale.com/ipn/ipnlocal
W 💣 tailscale.com/util/winutil/winenv from tailscale.com/hostinfo+
@ -336,7 +334,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
LD golang.org/x/crypto/ssh from tailscale.com/ipn/ipnlocal
LD golang.org/x/crypto/ssh/internal/bcrypt_pbkdf from golang.org/x/crypto/ssh
golang.org/x/exp/constraints from github.com/dblohm7/wingoes/pe+
golang.org/x/exp/constraints from tailscale.com/tsweb/varz+
golang.org/x/exp/maps from tailscale.com/ipn/store/mem+
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
golang.org/x/net/dns/dnsmessage from net+
@ -371,9 +369,8 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
golang.org/x/text/unicode/bidi from golang.org/x/net/idna+
golang.org/x/text/unicode/norm from golang.org/x/net/idna
golang.org/x/time/rate from gvisor.dev/gvisor/pkg/log+
archive/tar from tailscale.com/clientupdate
bufio from compress/flate+
bytes from archive/tar+
bytes from bufio+
cmp from encoding/json+
compress/flate from compress/gzip+
compress/gzip from golang.org/x/net/http2+
@ -446,7 +443,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
crypto/x509 from crypto/tls+
D crypto/x509/internal/macos from crypto/x509
crypto/x509/pkix from crypto/x509+
DW database/sql/driver from github.com/google/uuid
D database/sql/driver from github.com/google/uuid
W debug/dwarf from debug/pe
W debug/pe from github.com/dblohm7/wingoes/pe
embed from github.com/tailscale/web-client-prebuilt+
@ -459,11 +456,11 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
encoding/json from expvar+
encoding/pem from crypto/tls+
encoding/xml from github.com/tailscale/goupnp+
errors from archive/tar+
errors from bufio+
expvar from tailscale.com/health+
flag from tailscale.com/cmd/tsidp+
fmt from archive/tar+
hash from compress/zlib+
fmt from compress/flate+
hash from crypto+
W hash/adler32 from compress/zlib
hash/crc32 from compress/gzip+
hash/maphash from go4.org/mem
@ -480,7 +477,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
internal/filepathlite from os+
internal/fmtsort from fmt+
internal/goarch from crypto/internal/fips140deps/cpu+
internal/godebug from archive/tar+
internal/godebug from crypto/internal/fips140deps/godebug+
internal/godebugs from internal/godebug+
internal/goexperiment from hash/maphash+
internal/goos from crypto/x509+
@ -516,14 +513,14 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
internal/testlog from os
internal/trace/tracev2 from runtime+
internal/unsafeheader from internal/reflectlite+
io from archive/tar+
io/fs from archive/tar+
io from bufio+
io/fs from crypto/x509+
io/ioutil from github.com/digitalocean/go-smbios/smbios+
iter from bytes+
log from expvar+
log/internal from log
maps from archive/tar+
math from archive/tar+
maps from crypto/x509+
math from compress/flate+
math/big from crypto/dsa+
math/bits from bytes+
math/rand from github.com/fxamacker/cbor/v2+
@ -545,28 +542,28 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
os from crypto/internal/sysrand+
os/exec from github.com/godbus/dbus/v5+
os/signal from tailscale.com/cmd/tsidp
os/user from archive/tar+
path from archive/tar+
path/filepath from archive/tar+
reflect from archive/tar+
os/user from github.com/godbus/dbus/v5+
path from debug/dwarf+
path/filepath from crypto/x509+
reflect from crypto/x509+
regexp from github.com/tailscale/goupnp/httpu+
regexp/syntax from regexp
runtime from archive/tar+
runtime from crypto/internal/fips140+
runtime/debug from github.com/coder/websocket/internal/xsync+
runtime/pprof from net/http/pprof+
runtime/trace from net/http/pprof
slices from archive/tar+
slices from crypto/tls+
sort from compress/flate+
strconv from archive/tar+
strings from archive/tar+
strconv from compress/flate+
strings from bufio+
W structs from internal/syscall/windows
sync from archive/tar+
sync from compress/flate+
sync/atomic from context+
syscall from archive/tar+
syscall from crypto/internal/sysrand+
text/tabwriter from runtime/pprof
text/template from html/template
text/template/parse from html/template+
time from archive/tar+
time from compress/gzip+
unicode from bytes+
unicode/utf16 from crypto/x509+
unicode/utf8 from bufio+

View File

@ -0,0 +1,13 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Code generated by gen.go; DO NOT EDIT.
//go:build ts_omit_clientupdate
package buildfeatures
// HasClientUpdate is whether the binary was built with support for modular feature "Client auto-update support".
// Specifically, it's whether the binary was NOT built with the "ts_omit_clientupdate" build tag.
// It's a const so it can be used for dead code elimination.
const HasClientUpdate = false

View File

@ -0,0 +1,13 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Code generated by gen.go; DO NOT EDIT.
//go:build !ts_omit_clientupdate
package buildfeatures
// HasClientUpdate is whether the binary was built with support for modular feature "Client auto-update support".
// Specifically, it's whether the binary was NOT built with the "ts_omit_clientupdate" build tag.
// It's a const so it can be used for dead code elimination.
const HasClientUpdate = true

View File

@ -0,0 +1,530 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package clientupdate enables the client update feature.
package clientupdate
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
"time"
"tailscale.com/clientupdate"
"tailscale.com/envknob"
"tailscale.com/feature"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnext"
"tailscale.com/ipn/ipnlocal"
"tailscale.com/ipn/ipnstate"
"tailscale.com/ipn/localapi"
"tailscale.com/tailcfg"
"tailscale.com/types/logger"
"tailscale.com/util/httpm"
"tailscale.com/version"
"tailscale.com/version/distro"
)
func init() {
ipnext.RegisterExtension("clientupdate", newExt)
// C2N
ipnlocal.RegisterC2N("GET /update", handleC2NUpdateGet)
ipnlocal.RegisterC2N("POST /update", handleC2NUpdatePost)
// LocalAPI:
localapi.Register("update/install", serveUpdateInstall)
localapi.Register("update/progress", serveUpdateProgress)
}
func newExt(logf logger.Logf, sb ipnext.SafeBackend) (ipnext.Extension, error) {
return &extension{
logf: logf,
sb: sb,
lastSelfUpdateState: ipnstate.UpdateFinished,
}, nil
}
type extension struct {
logf logger.Logf
sb ipnext.SafeBackend
mu sync.Mutex
// c2nUpdateStatus is the status of c2n-triggered client update.
c2nUpdateStatus updateStatus
prefs ipn.PrefsView
state ipn.State
lastSelfUpdateState ipnstate.SelfUpdateStatus
selfUpdateProgress []ipnstate.UpdateProgress
// offlineAutoUpdateCancel stops offline auto-updates when called. It
// should be used via stopOfflineAutoUpdate and
// maybeStartOfflineAutoUpdate. It is nil when offline auto-updates are
// not running.
//
//lint:ignore U1000 only used in Linux and Windows builds in autoupdate.go
offlineAutoUpdateCancel func()
}
func (e *extension) Name() string { return "clientupdate" }
func (e *extension) Init(h ipnext.Host) error {
h.Hooks().ProfileStateChange.Add(e.onChangeProfile)
h.Hooks().BackendStateChange.Add(e.onBackendStateChange)
// TODO(nickkhyl): remove this after the profileManager refactoring.
// See tailscale/tailscale#15974.
// This same workaround appears in feature/portlist/portlist.go.
profile, prefs := h.Profiles().CurrentProfileState()
e.onChangeProfile(profile, prefs, false)
return nil
}
func (e *extension) Shutdown() error {
e.stopOfflineAutoUpdate()
return nil
}
func (e *extension) onBackendStateChange(newState ipn.State) {
e.mu.Lock()
defer e.mu.Unlock()
e.state = newState
e.updateOfflineAutoUpdateLocked()
}
func (e *extension) onChangeProfile(profile ipn.LoginProfileView, prefs ipn.PrefsView, sameNode bool) {
e.mu.Lock()
defer e.mu.Unlock()
e.prefs = prefs
e.updateOfflineAutoUpdateLocked()
}
func (e *extension) updateOfflineAutoUpdateLocked() {
want := e.prefs.Valid() && e.prefs.AutoUpdate().Apply.EqualBool(true) &&
e.state != ipn.Running && e.state != ipn.Starting
cur := e.offlineAutoUpdateCancel != nil
if want && !cur {
e.maybeStartOfflineAutoUpdateLocked(e.prefs)
} else if !want && cur {
e.stopOfflineAutoUpdateLocked()
}
}
type updateStatus struct {
started bool
}
func (e *extension) clearSelfUpdateProgress() {
e.mu.Lock()
defer e.mu.Unlock()
e.selfUpdateProgress = make([]ipnstate.UpdateProgress, 0)
e.lastSelfUpdateState = ipnstate.UpdateFinished
}
func (e *extension) GetSelfUpdateProgress() []ipnstate.UpdateProgress {
e.mu.Lock()
defer e.mu.Unlock()
res := make([]ipnstate.UpdateProgress, len(e.selfUpdateProgress))
copy(res, e.selfUpdateProgress)
return res
}
func (e *extension) DoSelfUpdate() {
e.mu.Lock()
updateState := e.lastSelfUpdateState
e.mu.Unlock()
// don't start an update if one is already in progress
if updateState == ipnstate.UpdateInProgress {
return
}
e.clearSelfUpdateProgress()
e.pushSelfUpdateProgress(ipnstate.NewUpdateProgress(ipnstate.UpdateInProgress, ""))
up, err := clientupdate.NewUpdater(clientupdate.Arguments{
Logf: func(format string, args ...any) {
e.pushSelfUpdateProgress(ipnstate.NewUpdateProgress(ipnstate.UpdateInProgress, fmt.Sprintf(format, args...)))
},
})
if err != nil {
e.pushSelfUpdateProgress(ipnstate.NewUpdateProgress(ipnstate.UpdateFailed, err.Error()))
}
err = up.Update()
if err != nil {
e.pushSelfUpdateProgress(ipnstate.NewUpdateProgress(ipnstate.UpdateFailed, err.Error()))
} else {
e.pushSelfUpdateProgress(ipnstate.NewUpdateProgress(ipnstate.UpdateFinished, "tailscaled did not restart; please restart Tailscale manually."))
}
}
// serveUpdateInstall sends a request to the LocalBackend to start a Tailscale
// self-update. A successful response does not indicate whether the update
// succeeded, only that the request was accepted. Clients should use
// serveUpdateProgress after pinging this endpoint to check how the update is
// going.
func serveUpdateInstall(h *localapi.Handler, w http.ResponseWriter, r *http.Request) {
if r.Method != httpm.POST {
http.Error(w, "only POST allowed", http.StatusMethodNotAllowed)
return
}
b := h.LocalBackend()
ext, ok := ipnlocal.GetExt[*extension](b)
if !ok {
http.Error(w, "clientupdate extension not found", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusAccepted)
go ext.DoSelfUpdate()
}
// serveUpdateProgress returns the status of an in-progress Tailscale self-update.
// This is provided as a slice of ipnstate.UpdateProgress structs with various
// log messages in order from oldest to newest. If an update is not in progress,
// the returned slice will be empty.
func serveUpdateProgress(h *localapi.Handler, w http.ResponseWriter, r *http.Request) {
if r.Method != httpm.GET {
http.Error(w, "only GET allowed", http.StatusMethodNotAllowed)
return
}
b := h.LocalBackend()
ext, ok := ipnlocal.GetExt[*extension](b)
if !ok {
http.Error(w, "clientupdate extension not found", http.StatusInternalServerError)
return
}
ups := ext.GetSelfUpdateProgress()
json.NewEncoder(w).Encode(ups)
}
func (e *extension) pushSelfUpdateProgress(up ipnstate.UpdateProgress) {
e.mu.Lock()
defer e.mu.Unlock()
e.selfUpdateProgress = append(e.selfUpdateProgress, up)
e.lastSelfUpdateState = up.Status
}
func handleC2NUpdateGet(b *ipnlocal.LocalBackend, w http.ResponseWriter, r *http.Request) {
e, ok := ipnlocal.GetExt[*extension](b)
if !ok {
http.Error(w, "clientupdate extension not found", http.StatusInternalServerError)
return
}
e.logf("c2n: GET /update received")
res := e.newC2NUpdateResponse()
res.Started = e.c2nUpdateStarted()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(res)
}
func handleC2NUpdatePost(b *ipnlocal.LocalBackend, w http.ResponseWriter, r *http.Request) {
e, ok := ipnlocal.GetExt[*extension](b)
if !ok {
http.Error(w, "clientupdate extension not found", http.StatusInternalServerError)
return
}
e.logf("c2n: POST /update received")
res := e.newC2NUpdateResponse()
defer func() {
if res.Err != "" {
e.logf("c2n: POST /update failed: %s", res.Err)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(res)
}()
if !res.Enabled {
res.Err = "not enabled"
return
}
if !res.Supported {
res.Err = "not supported"
return
}
// Do not update if we have active inbound SSH connections. Control can set
// force=true query parameter to override this.
if r.FormValue("force") != "true" && b.ActiveSSHConns() > 0 {
res.Err = "not updating due to active SSH connections"
return
}
if err := e.startAutoUpdate("c2n"); err != nil {
res.Err = err.Error()
return
}
res.Started = true
}
func (e *extension) newC2NUpdateResponse() tailcfg.C2NUpdateResponse {
e.mu.Lock()
defer e.mu.Unlock()
// If NewUpdater does not return an error, we can update the installation.
//
// Note that we create the Updater solely to check for errors; we do not
// invoke it here. For this purpose, it is ok to pass it a zero Arguments.
var upPref ipn.AutoUpdatePrefs
if e.prefs.Valid() {
upPref = e.prefs.AutoUpdate()
}
return tailcfg.C2NUpdateResponse{
Enabled: envknob.AllowsRemoteUpdate() || upPref.Apply.EqualBool(true),
Supported: feature.CanAutoUpdate() && !version.IsMacSysExt(),
}
}
func (e *extension) c2nUpdateStarted() bool {
e.mu.Lock()
defer e.mu.Unlock()
return e.c2nUpdateStatus.started
}
func (e *extension) setC2NUpdateStarted(v bool) {
e.mu.Lock()
defer e.mu.Unlock()
e.c2nUpdateStatus.started = v
}
func (e *extension) trySetC2NUpdateStarted() bool {
e.mu.Lock()
defer e.mu.Unlock()
if e.c2nUpdateStatus.started {
return false
}
e.c2nUpdateStatus.started = true
return true
}
// findCmdTailscale looks for the cmd/tailscale that corresponds to the
// currently running cmd/tailscaled. It's up to the caller to verify that the
// two match, but this function does its best to find the right one. Notably, it
// doesn't use $PATH for security reasons.
func findCmdTailscale() (string, error) {
self, err := os.Executable()
if err != nil {
return "", err
}
var ts string
switch runtime.GOOS {
case "linux":
if self == "/usr/sbin/tailscaled" || self == "/usr/bin/tailscaled" {
ts = "/usr/bin/tailscale"
}
if self == "/usr/local/sbin/tailscaled" || self == "/usr/local/bin/tailscaled" {
ts = "/usr/local/bin/tailscale"
}
switch distro.Get() {
case distro.QNAP:
// The volume under /share/ where qpkg are installed is not
// predictable. But the rest of the path is.
ok, err := filepath.Match("/share/*/.qpkg/Tailscale/tailscaled", self)
if err == nil && ok {
ts = filepath.Join(filepath.Dir(self), "tailscale")
}
case distro.Unraid:
if self == "/usr/local/emhttp/plugins/tailscale/bin/tailscaled" {
ts = "/usr/local/emhttp/plugins/tailscale/bin/tailscale"
}
}
case "windows":
ts = filepath.Join(filepath.Dir(self), "tailscale.exe")
case "freebsd", "openbsd":
if self == "/usr/local/bin/tailscaled" {
ts = "/usr/local/bin/tailscale"
}
default:
return "", fmt.Errorf("unsupported OS %v", runtime.GOOS)
}
if ts != "" && regularFileExists(ts) {
return ts, nil
}
return "", errors.New("tailscale executable not found in expected place")
}
func tailscaleUpdateCmd(cmdTS string) *exec.Cmd {
defaultCmd := exec.Command(cmdTS, "update", "--yes")
if runtime.GOOS != "linux" {
return defaultCmd
}
if _, err := exec.LookPath("systemd-run"); err != nil {
return defaultCmd
}
// When systemd-run is available, use it to run the update command. This
// creates a new temporary unit separate from the tailscaled unit. When
// tailscaled is restarted during the update, systemd won't kill this
// temporary update unit, which could cause unexpected breakage.
//
// We want to use a few optional flags:
// * --wait, to block the update command until completion (added in systemd 232)
// * --pipe, to collect stdout/stderr (added in systemd 235)
// * --collect, to clean up failed runs from memory (added in systemd 236)
//
// We need to check the version of systemd to figure out if those flags are
// available.
//
// The output will look like:
//
// systemd 255 (255.7-1-arch)
// +PAM +AUDIT ... other feature flags ...
systemdVerOut, err := exec.Command("systemd-run", "--version").Output()
if err != nil {
return defaultCmd
}
parts := strings.Fields(string(systemdVerOut))
if len(parts) < 2 || parts[0] != "systemd" {
return defaultCmd
}
systemdVer, err := strconv.Atoi(parts[1])
if err != nil {
return defaultCmd
}
if systemdVer >= 236 {
return exec.Command("systemd-run", "--wait", "--pipe", "--collect", cmdTS, "update", "--yes")
} else if systemdVer >= 235 {
return exec.Command("systemd-run", "--wait", "--pipe", cmdTS, "update", "--yes")
} else if systemdVer >= 232 {
return exec.Command("systemd-run", "--wait", cmdTS, "update", "--yes")
} else {
return exec.Command("systemd-run", cmdTS, "update", "--yes")
}
}
func regularFileExists(path string) bool {
fi, err := os.Stat(path)
return err == nil && fi.Mode().IsRegular()
}
// startAutoUpdate triggers an auto-update attempt. The actual update happens
// asynchronously. If another update is in progress, an error is returned.
func (e *extension) startAutoUpdate(logPrefix string) (retErr error) {
// Check if update was already started, and mark as started.
if !e.trySetC2NUpdateStarted() {
return errors.New("update already started")
}
defer func() {
// Clear the started flag if something failed.
if retErr != nil {
e.setC2NUpdateStarted(false)
}
}()
cmdTS, err := findCmdTailscale()
if err != nil {
return fmt.Errorf("failed to find cmd/tailscale binary: %w", err)
}
var ver struct {
Long string `json:"long"`
}
out, err := exec.Command(cmdTS, "version", "--json").Output()
if err != nil {
return fmt.Errorf("failed to find cmd/tailscale binary: %w", err)
}
if err := json.Unmarshal(out, &ver); err != nil {
return fmt.Errorf("invalid JSON from cmd/tailscale version --json: %w", err)
}
if ver.Long != version.Long() {
return fmt.Errorf("cmd/tailscale version %q does not match tailscaled version %q", ver.Long, version.Long())
}
cmd := tailscaleUpdateCmd(cmdTS)
buf := new(bytes.Buffer)
cmd.Stdout = buf
cmd.Stderr = buf
e.logf("%s: running %q", logPrefix, strings.Join(cmd.Args, " "))
if err := cmd.Start(); err != nil {
return fmt.Errorf("failed to start cmd/tailscale update: %w", err)
}
go func() {
if err := cmd.Wait(); err != nil {
e.logf("%s: update command failed: %v, output: %s", logPrefix, err, buf)
} else {
e.logf("%s: update attempt complete", logPrefix)
}
e.setC2NUpdateStarted(false)
}()
return nil
}
func (e *extension) stopOfflineAutoUpdate() {
e.mu.Lock()
defer e.mu.Unlock()
e.stopOfflineAutoUpdateLocked()
}
func (e *extension) stopOfflineAutoUpdateLocked() {
if e.offlineAutoUpdateCancel == nil {
return
}
e.logf("offline auto-update: stopping update checks")
e.offlineAutoUpdateCancel()
e.offlineAutoUpdateCancel = nil
}
// e.mu must be held
func (e *extension) maybeStartOfflineAutoUpdateLocked(prefs ipn.PrefsView) {
if !prefs.Valid() || !prefs.AutoUpdate().Apply.EqualBool(true) {
return
}
// AutoUpdate.Apply field in prefs can only be true for platforms that
// support auto-updates. But check it here again, just in case.
if !feature.CanAutoUpdate() {
return
}
// On macsys, auto-updates are managed by Sparkle.
if version.IsMacSysExt() {
return
}
if e.offlineAutoUpdateCancel != nil {
// Already running.
return
}
ctx, cancel := context.WithCancel(context.Background())
e.offlineAutoUpdateCancel = cancel
e.logf("offline auto-update: starting update checks")
go e.offlineAutoUpdate(ctx)
}
const offlineAutoUpdateCheckPeriod = time.Hour
func (e *extension) offlineAutoUpdate(ctx context.Context) {
t := time.NewTicker(offlineAutoUpdateCheckPeriod)
defer t.Stop()
for {
select {
case <-ctx.Done():
return
case <-t.C:
}
if err := e.startAutoUpdate("offline auto-update"); err != nil {
e.logf("offline auto-update: failed: %v", err)
}
}
}

View File

@ -0,0 +1,8 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !ts_omit_clientupdate
package condregister
import _ "tailscale.com/feature/clientupdate"

View File

@ -97,6 +97,7 @@ var Features = map[FeatureTag]FeatureMeta{
"capture": {"Capture", "Packet capture", nil},
"cli": {"CLI", "embed the CLI into the tailscaled binary", nil},
"cliconndiag": {"CLIConnDiag", "CLI connection error diagnostics", nil},
"clientupdate": {"ClientUpdate", "Client auto-update support", nil},
"completion": {"Completion", "CLI shell completion", nil},
"dbus": {"DBus", "Linux DBus support", nil},
"debugeventbus": {"DebugEventBus", "eventbus debug support", nil},

17
feature/hooks.go Normal file
View File

@ -0,0 +1,17 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package feature
// HookCanAutoUpdate is a hook for the clientupdate package
// to conditionally initialize.
var HookCanAutoUpdate Hook[func() bool]
// CanAutoUpdate reports whether the current binary is built with auto-update
// support and, if so, whether the current platform supports it.
func CanAutoUpdate() bool {
if f, ok := HookCanAutoUpdate.GetOk(); ok {
return f()
}
return false
}

View File

@ -1,65 +0,0 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build linux || windows
package ipnlocal
import (
"context"
"time"
"tailscale.com/clientupdate"
"tailscale.com/ipn"
"tailscale.com/version"
)
func (b *LocalBackend) stopOfflineAutoUpdate() {
if b.offlineAutoUpdateCancel != nil {
b.logf("offline auto-update: stopping update checks")
b.offlineAutoUpdateCancel()
b.offlineAutoUpdateCancel = nil
}
}
func (b *LocalBackend) maybeStartOfflineAutoUpdate(prefs ipn.PrefsView) {
if !prefs.AutoUpdate().Apply.EqualBool(true) {
return
}
// AutoUpdate.Apply field in prefs can only be true for platforms that
// support auto-updates. But check it here again, just in case.
if !clientupdate.CanAutoUpdate() {
return
}
// On macsys, auto-updates are managed by Sparkle.
if version.IsMacSysExt() {
return
}
if b.offlineAutoUpdateCancel != nil {
// Already running.
return
}
ctx, cancel := context.WithCancel(context.Background())
b.offlineAutoUpdateCancel = cancel
b.logf("offline auto-update: starting update checks")
go b.offlineAutoUpdate(ctx)
}
const offlineAutoUpdateCheckPeriod = time.Hour
func (b *LocalBackend) offlineAutoUpdate(ctx context.Context) {
t := time.NewTicker(offlineAutoUpdateCheckPeriod)
defer t.Stop()
for {
select {
case <-ctx.Done():
return
case <-t.C:
}
if err := b.startAutoUpdate("offline auto-update"); err != nil {
b.logf("offline auto-update: failed: %v", err)
}
}
}

View File

@ -1,18 +0,0 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !(linux || windows)
package ipnlocal
import (
"tailscale.com/ipn"
)
func (b *LocalBackend) stopOfflineAutoUpdate() {
// Not supported on this platform.
}
func (b *LocalBackend) maybeStartOfflineAutoUpdate(prefs ipn.PrefsView) {
// Not supported on this platform.
}

View File

@ -5,23 +5,16 @@ package ipnlocal
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"reflect"
"runtime"
"strconv"
"strings"
"time"
"tailscale.com/clientupdate"
"tailscale.com/control/controlclient"
"tailscale.com/envknob"
"tailscale.com/ipn"
"tailscale.com/net/sockstats"
"tailscale.com/posture"
@ -34,7 +27,6 @@ import (
"tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/ptype"
"tailscale.com/version"
"tailscale.com/version/distro"
)
// c2nHandlers maps an HTTP method and URI path (without query parameters) to
@ -60,10 +52,6 @@ var c2nHandlers = map[methodAndPath]c2nHandler{
// SSH
req("/ssh/usernames"): handleC2NSSHUsernames,
// Auto-updates.
req("GET /update"): handleC2NUpdateGet,
req("POST /update"): handleC2NUpdatePost,
// Device posture.
req("GET /posture/identity"): handleC2NPostureIdentityGet,
@ -337,50 +325,6 @@ func handleC2NSetNetfilterKind(b *LocalBackend, w http.ResponseWriter, r *http.R
w.WriteHeader(http.StatusNoContent)
}
func handleC2NUpdateGet(b *LocalBackend, w http.ResponseWriter, r *http.Request) {
b.logf("c2n: GET /update received")
res := b.newC2NUpdateResponse()
res.Started = b.c2nUpdateStarted()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(res)
}
func handleC2NUpdatePost(b *LocalBackend, w http.ResponseWriter, r *http.Request) {
b.logf("c2n: POST /update received")
res := b.newC2NUpdateResponse()
defer func() {
if res.Err != "" {
b.logf("c2n: POST /update failed: %s", res.Err)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(res)
}()
if !res.Enabled {
res.Err = "not enabled"
return
}
if !res.Supported {
res.Err = "not supported"
return
}
// Do not update if we have active inbound SSH connections. Control can set
// force=true query parameter to override this.
if r.FormValue("force") != "true" && b.sshServer != nil && b.sshServer.NumActiveConns() > 0 {
res.Err = "not updating due to active SSH connections"
return
}
if err := b.startAutoUpdate("c2n"); err != nil {
res.Err = err.Error()
return
}
res.Started = true
}
func handleC2NPostureIdentityGet(b *LocalBackend, w http.ResponseWriter, r *http.Request) {
b.logf("c2n: GET /posture/identity received")
@ -423,137 +367,3 @@ func handleC2NPostureIdentityGet(b *LocalBackend, w http.ResponseWriter, r *http
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(res)
}
func (b *LocalBackend) newC2NUpdateResponse() tailcfg.C2NUpdateResponse {
// If NewUpdater does not return an error, we can update the installation.
//
// Note that we create the Updater solely to check for errors; we do not
// invoke it here. For this purpose, it is ok to pass it a zero Arguments.
prefs := b.Prefs().AutoUpdate()
return tailcfg.C2NUpdateResponse{
Enabled: envknob.AllowsRemoteUpdate() || prefs.Apply.EqualBool(true),
Supported: clientupdate.CanAutoUpdate() && !version.IsMacSysExt(),
}
}
func (b *LocalBackend) c2nUpdateStarted() bool {
b.mu.Lock()
defer b.mu.Unlock()
return b.c2nUpdateStatus.started
}
func (b *LocalBackend) setC2NUpdateStarted(v bool) {
b.mu.Lock()
defer b.mu.Unlock()
b.c2nUpdateStatus.started = v
}
func (b *LocalBackend) trySetC2NUpdateStarted() bool {
b.mu.Lock()
defer b.mu.Unlock()
if b.c2nUpdateStatus.started {
return false
}
b.c2nUpdateStatus.started = true
return true
}
// findCmdTailscale looks for the cmd/tailscale that corresponds to the
// currently running cmd/tailscaled. It's up to the caller to verify that the
// two match, but this function does its best to find the right one. Notably, it
// doesn't use $PATH for security reasons.
func findCmdTailscale() (string, error) {
self, err := os.Executable()
if err != nil {
return "", err
}
var ts string
switch runtime.GOOS {
case "linux":
if self == "/usr/sbin/tailscaled" || self == "/usr/bin/tailscaled" {
ts = "/usr/bin/tailscale"
}
if self == "/usr/local/sbin/tailscaled" || self == "/usr/local/bin/tailscaled" {
ts = "/usr/local/bin/tailscale"
}
switch distro.Get() {
case distro.QNAP:
// The volume under /share/ where qpkg are installed is not
// predictable. But the rest of the path is.
ok, err := filepath.Match("/share/*/.qpkg/Tailscale/tailscaled", self)
if err == nil && ok {
ts = filepath.Join(filepath.Dir(self), "tailscale")
}
case distro.Unraid:
if self == "/usr/local/emhttp/plugins/tailscale/bin/tailscaled" {
ts = "/usr/local/emhttp/plugins/tailscale/bin/tailscale"
}
}
case "windows":
ts = filepath.Join(filepath.Dir(self), "tailscale.exe")
case "freebsd", "openbsd":
if self == "/usr/local/bin/tailscaled" {
ts = "/usr/local/bin/tailscale"
}
default:
return "", fmt.Errorf("unsupported OS %v", runtime.GOOS)
}
if ts != "" && regularFileExists(ts) {
return ts, nil
}
return "", errors.New("tailscale executable not found in expected place")
}
func tailscaleUpdateCmd(cmdTS string) *exec.Cmd {
defaultCmd := exec.Command(cmdTS, "update", "--yes")
if runtime.GOOS != "linux" {
return defaultCmd
}
if _, err := exec.LookPath("systemd-run"); err != nil {
return defaultCmd
}
// When systemd-run is available, use it to run the update command. This
// creates a new temporary unit separate from the tailscaled unit. When
// tailscaled is restarted during the update, systemd won't kill this
// temporary update unit, which could cause unexpected breakage.
//
// We want to use a few optional flags:
// * --wait, to block the update command until completion (added in systemd 232)
// * --pipe, to collect stdout/stderr (added in systemd 235)
// * --collect, to clean up failed runs from memory (added in systemd 236)
//
// We need to check the version of systemd to figure out if those flags are
// available.
//
// The output will look like:
//
// systemd 255 (255.7-1-arch)
// +PAM +AUDIT ... other feature flags ...
systemdVerOut, err := exec.Command("systemd-run", "--version").Output()
if err != nil {
return defaultCmd
}
parts := strings.Fields(string(systemdVerOut))
if len(parts) < 2 || parts[0] != "systemd" {
return defaultCmd
}
systemdVer, err := strconv.Atoi(parts[1])
if err != nil {
return defaultCmd
}
if systemdVer >= 236 {
return exec.Command("systemd-run", "--wait", "--pipe", "--collect", cmdTS, "update", "--yes")
} else if systemdVer >= 235 {
return exec.Command("systemd-run", "--wait", "--pipe", cmdTS, "update", "--yes")
} else if systemdVer >= 232 {
return exec.Command("systemd-run", "--wait", cmdTS, "update", "--yes")
} else {
return exec.Command("systemd-run", cmdTS, "update", "--yes")
}
}
func regularFileExists(path string) bool {
fi, err := os.Stat(path)
return err == nil && fi.Mode().IsRegular()
}

View File

@ -6,7 +6,6 @@
package ipnlocal
import (
"bytes"
"cmp"
"context"
"crypto/sha256"
@ -25,7 +24,6 @@ import (
"net/netip"
"net/url"
"os"
"os/exec"
"reflect"
"runtime"
"slices"
@ -40,7 +38,6 @@ import (
"golang.org/x/net/dns/dnsmessage"
"tailscale.com/appc"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/clientupdate"
"tailscale.com/control/controlclient"
"tailscale.com/control/controlknobs"
"tailscale.com/drive"
@ -302,22 +299,11 @@ type LocalBackend struct {
notifyWatchers map[string]*watchSession // by session ID
lastStatusTime time.Time // status.AsOf value of the last processed status update
componentLogUntil map[string]componentLogState
// c2nUpdateStatus is the status of c2n-triggered client update.
c2nUpdateStatus updateStatus
currentUser ipnauth.Actor
currentUser ipnauth.Actor
selfUpdateProgress []ipnstate.UpdateProgress
lastSelfUpdateState ipnstate.SelfUpdateStatus
// capForcedNetfilter is the netfilter that control instructs Linux clients
// to use, unless overridden locally.
capForcedNetfilter string // TODO(nickkhyl): move to nodeBackend
// offlineAutoUpdateCancel stops offline auto-updates when called. It
// should be used via stopOfflineAutoUpdate and
// maybeStartOfflineAutoUpdate. It is nil when offline auto-updates are
// note running.
//
//lint:ignore U1000 only used in Linux and Windows builds in autoupdate.go
offlineAutoUpdateCancel func()
// ServeConfig fields. (also guarded by mu)
lastServeConfJSON mem.RO // last JSON that was parsed into serveConfig
@ -433,10 +419,6 @@ func (b *LocalBackend) NetMon() *netmon.Monitor {
return b.sys.NetMon.Get()
}
type updateStatus struct {
started bool
}
type metrics struct {
// advertisedRoutes is a metric that reports the number of network routes that are advertised by the local node.
// This informs the user of how many routes are being advertised by the local node, excluding exit routes.
@ -517,8 +499,6 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
em: newExpiryManager(logf, sys.Bus.Get()),
loginFlags: loginFlags,
clock: clock,
selfUpdateProgress: make([]ipnstate.UpdateProgress, 0),
lastSelfUpdateState: ipnstate.UpdateFinished,
captiveCtx: captiveCtx,
captiveCancel: nil, // so that we start checkCaptivePortalLoop when Running
needsCaptiveDetection: make(chan bool),
@ -1127,7 +1107,6 @@ func (b *LocalBackend) Shutdown() {
defer cancel()
b.sockstatLogger.Shutdown(ctx)
}
b.stopOfflineAutoUpdate()
b.unregisterSysPolicyWatch()
if cc != nil {
@ -3412,7 +3391,7 @@ func (b *LocalBackend) onTailnetDefaultAutoUpdate(au bool) {
// can still manually enable auto-updates on this node.
return
}
if clientupdate.CanAutoUpdate() {
if buildfeatures.HasClientUpdate && feature.CanAutoUpdate() {
b.logf("using tailnet default auto-update setting: %v", au)
prefsClone := prefs.AsStruct()
prefsClone.AutoUpdate.Apply = opt.NewBool(au)
@ -4100,7 +4079,12 @@ func (b *LocalBackend) checkFunnelEnabledLocked(p *ipn.Prefs) error {
}
func (b *LocalBackend) checkAutoUpdatePrefsLocked(p *ipn.Prefs) error {
if p.AutoUpdate.Apply.EqualBool(true) && !clientupdate.CanAutoUpdate() {
if !buildfeatures.HasClientUpdate {
if p.AutoUpdate.Apply.EqualBool(true) {
return errors.New("Auto-update support is disabled in this build")
}
}
if p.AutoUpdate.Apply.EqualBool(true) && !feature.CanAutoUpdate() {
return errors.New("Auto-updates are not supported on this platform.")
}
return nil
@ -4552,14 +4536,6 @@ func (b *LocalBackend) setPrefsLockedOnEntry(newp *ipn.Prefs, unlock unlockOnce)
b.resetAlwaysOnOverrideLocked()
}
if newp.AutoUpdate.Apply.EqualBool(true) {
if b.state != ipn.Running {
b.maybeStartOfflineAutoUpdate(newp.View())
}
} else {
b.stopOfflineAutoUpdate()
}
unlock.UnlockEarly()
if oldp.ShieldsUp() != newp.ShieldsUp || hostInfoChanged {
@ -5467,12 +5443,6 @@ func (b *LocalBackend) enterStateLockedOnEntry(newState ipn.State, unlock unlock
}
b.pauseOrResumeControlClientLocked()
if newState == ipn.Running {
b.stopOfflineAutoUpdate()
} else {
b.maybeStartOfflineAutoUpdate(prefs)
}
unlock.UnlockEarly()
// prefs may change irrespective of state; WantRunning should be explicitly
@ -6611,6 +6581,15 @@ func (b *LocalBackend) DoNoiseRequest(req *http.Request) (*http.Response, error)
return cc.DoNoiseRequest(req)
}
// ActiveSSHConns returns the number of active SSH connections,
// or 0 if SSH is not linked into the binary or available on the platform.
func (b *LocalBackend) ActiveSSHConns() int {
if b.sshServer == nil {
return 0
}
return b.sshServer.NumActiveConns()
}
func (b *LocalBackend) sshServerOrInit() (_ SSHServer, err error) {
b.mu.Lock()
defer b.mu.Unlock()
@ -6941,54 +6920,6 @@ func (b *LocalBackend) DebugBreakDERPConns() error {
return b.MagicConn().DebugBreakDERPConns()
}
func (b *LocalBackend) pushSelfUpdateProgress(up ipnstate.UpdateProgress) {
b.mu.Lock()
defer b.mu.Unlock()
b.selfUpdateProgress = append(b.selfUpdateProgress, up)
b.lastSelfUpdateState = up.Status
}
func (b *LocalBackend) clearSelfUpdateProgress() {
b.mu.Lock()
defer b.mu.Unlock()
b.selfUpdateProgress = make([]ipnstate.UpdateProgress, 0)
b.lastSelfUpdateState = ipnstate.UpdateFinished
}
func (b *LocalBackend) GetSelfUpdateProgress() []ipnstate.UpdateProgress {
b.mu.Lock()
defer b.mu.Unlock()
res := make([]ipnstate.UpdateProgress, len(b.selfUpdateProgress))
copy(res, b.selfUpdateProgress)
return res
}
func (b *LocalBackend) DoSelfUpdate() {
b.mu.Lock()
updateState := b.lastSelfUpdateState
b.mu.Unlock()
// don't start an update if one is already in progress
if updateState == ipnstate.UpdateInProgress {
return
}
b.clearSelfUpdateProgress()
b.pushSelfUpdateProgress(ipnstate.NewUpdateProgress(ipnstate.UpdateInProgress, ""))
up, err := clientupdate.NewUpdater(clientupdate.Arguments{
Logf: func(format string, args ...any) {
b.pushSelfUpdateProgress(ipnstate.NewUpdateProgress(ipnstate.UpdateInProgress, fmt.Sprintf(format, args...)))
},
})
if err != nil {
b.pushSelfUpdateProgress(ipnstate.NewUpdateProgress(ipnstate.UpdateFailed, err.Error()))
}
err = up.Update()
if err != nil {
b.pushSelfUpdateProgress(ipnstate.NewUpdateProgress(ipnstate.UpdateFailed, err.Error()))
} else {
b.pushSelfUpdateProgress(ipnstate.NewUpdateProgress(ipnstate.UpdateFinished, "tailscaled did not restart; please restart Tailscale manually."))
}
}
// ObserveDNSResponse passes a DNS response from the PeerAPI DNS server to the
// App Connector to enable route discovery.
func (b *LocalBackend) ObserveDNSResponse(res []byte) error {
@ -7603,58 +7534,6 @@ func isAllowedAutoExitNodeID(polc policyclient.Client, exitNodeID tailcfg.Stable
return true // no policy configured; allow all exit nodes
}
// startAutoUpdate triggers an auto-update attempt. The actual update happens
// asynchronously. If another update is in progress, an error is returned.
func (b *LocalBackend) startAutoUpdate(logPrefix string) (retErr error) {
// Check if update was already started, and mark as started.
if !b.trySetC2NUpdateStarted() {
return errors.New("update already started")
}
defer func() {
// Clear the started flag if something failed.
if retErr != nil {
b.setC2NUpdateStarted(false)
}
}()
cmdTS, err := findCmdTailscale()
if err != nil {
return fmt.Errorf("failed to find cmd/tailscale binary: %w", err)
}
var ver struct {
Long string `json:"long"`
}
out, err := exec.Command(cmdTS, "version", "--json").Output()
if err != nil {
return fmt.Errorf("failed to find cmd/tailscale binary: %w", err)
}
if err := json.Unmarshal(out, &ver); err != nil {
return fmt.Errorf("invalid JSON from cmd/tailscale version --json: %w", err)
}
if ver.Long != version.Long() {
return fmt.Errorf("cmd/tailscale version %q does not match tailscaled version %q", ver.Long, version.Long())
}
cmd := tailscaleUpdateCmd(cmdTS)
buf := new(bytes.Buffer)
cmd.Stdout = buf
cmd.Stderr = buf
b.logf("%s: running %q", logPrefix, strings.Join(cmd.Args, " "))
if err := cmd.Start(); err != nil {
return fmt.Errorf("failed to start cmd/tailscale update: %w", err)
}
go func() {
if err := cmd.Wait(); err != nil {
b.logf("%s: update command failed: %v, output: %s", logPrefix, err, buf)
} else {
b.logf("%s: update attempt complete", logPrefix)
}
b.setC2NUpdateStarted(false)
}()
return nil
}
// srcIPHasCapForFilter is called by the packet filter when evaluating firewall
// rules that require a source IP to have a certain node capability.
//

View File

@ -30,10 +30,10 @@ import (
"golang.org/x/net/dns/dnsmessage"
"tailscale.com/appc"
"tailscale.com/appc/appctest"
"tailscale.com/clientupdate"
"tailscale.com/control/controlclient"
"tailscale.com/drive"
"tailscale.com/drive/driveimpl"
"tailscale.com/feature"
_ "tailscale.com/feature/condregister/portmapper"
"tailscale.com/health"
"tailscale.com/hostinfo"
@ -3710,7 +3710,7 @@ func TestOnTailnetDefaultAutoUpdate(t *testing.T) {
// On platforms that don't support auto-update we can never
// transition to auto-updates being enabled. The value should
// remain unchanged after onTailnetDefaultAutoUpdate.
if !clientupdate.CanAutoUpdate() {
if !feature.CanAutoUpdate() {
want = tt.before
}
if got := b.pm.CurrentPrefs().AutoUpdate().Apply; got != want {
@ -5455,7 +5455,7 @@ func TestEnableAutoUpdates(t *testing.T) {
})
// Enabling may fail, depending on which environment we are running this
// test in.
wantErr := !clientupdate.CanAutoUpdate()
wantErr := !feature.CanAutoUpdate()
gotErr := err != nil
if gotErr != wantErr {
t.Fatalf("enabling auto-updates: got error: %v (%v); want error: %v", gotErr, err, wantErr)

View File

@ -13,8 +13,8 @@ import (
"slices"
"strings"
"tailscale.com/clientupdate"
"tailscale.com/envknob"
"tailscale.com/feature"
"tailscale.com/health"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnext"
@ -674,7 +674,7 @@ func (pm *profileManager) loadSavedPrefs(key ipn.StateKey) (ipn.PrefsView, error
// cause any EditPrefs calls to fail (other than disabling auto-updates).
//
// Reset AutoUpdate.Apply if we detect such invalid prefs.
if savedPrefs.AutoUpdate.Apply.EqualBool(true) && !clientupdate.CanAutoUpdate() {
if savedPrefs.AutoUpdate.Apply.EqualBool(true) && !feature.CanAutoUpdate() {
savedPrefs.AutoUpdate.Apply.Clear()
}

View File

@ -12,7 +12,8 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"tailscale.com/clientupdate"
_ "tailscale.com/clientupdate" // for feature registration side effects
"tailscale.com/feature"
"tailscale.com/health"
"tailscale.com/ipn"
"tailscale.com/ipn/store/mem"
@ -464,7 +465,7 @@ func TestProfileManagement(t *testing.T) {
wantCurProfile = "user@2.example.com"
checkProfiles(t)
if !clientupdate.CanAutoUpdate() {
if !feature.CanAutoUpdate() {
t.Logf("Save an invalid AutoUpdate pref value")
prefs := pm.CurrentPrefs().AsStruct()
prefs.AutoUpdate.Apply.Set(true)

View File

@ -27,8 +27,8 @@ import (
"golang.org/x/net/dns/dnsmessage"
"tailscale.com/appc"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/clientupdate"
"tailscale.com/envknob"
"tailscale.com/feature"
"tailscale.com/feature/buildfeatures"
"tailscale.com/health/healthmsg"
"tailscale.com/hostinfo"
@ -120,8 +120,6 @@ var handler = map[string]LocalAPIHandler{
"status": (*Handler).serveStatus,
"suggest-exit-node": (*Handler).serveSuggestExitNode,
"update/check": (*Handler).serveUpdateCheck,
"update/install": (*Handler).serveUpdateInstall,
"update/progress": (*Handler).serveUpdateProgress,
"upload-client-metrics": (*Handler).serveUploadClientMetrics,
"usermetrics": (*Handler).serveUserMetrics,
"watch-ipn-bus": (*Handler).serveWatchIPNBus,
@ -1897,7 +1895,7 @@ func (h *Handler) serveUpdateCheck(w http.ResponseWriter, r *http.Request) {
return
}
if !clientupdate.CanAutoUpdate() {
if !feature.CanAutoUpdate() {
// if we don't support auto-update, just say that we're up to date
json.NewEncoder(w).Encode(tailcfg.ClientVersion{RunningLatest: true})
return
@ -1915,37 +1913,6 @@ func (h *Handler) serveUpdateCheck(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(cv)
}
// serveUpdateInstall sends a request to the LocalBackend to start a Tailscale
// self-update. A successful response does not indicate whether the update
// succeeded, only that the request was accepted. Clients should use
// serveUpdateProgress after pinging this endpoint to check how the update is
// going.
func (h *Handler) serveUpdateInstall(w http.ResponseWriter, r *http.Request) {
if r.Method != httpm.POST {
http.Error(w, "only POST allowed", http.StatusMethodNotAllowed)
return
}
w.WriteHeader(http.StatusAccepted)
go h.b.DoSelfUpdate()
}
// serveUpdateProgress returns the status of an in-progress Tailscale self-update.
// This is provided as a slice of ipnstate.UpdateProgress structs with various
// log messages in order from oldest to newest. If an update is not in progress,
// the returned slice will be empty.
func (h *Handler) serveUpdateProgress(w http.ResponseWriter, r *http.Request) {
if r.Method != httpm.GET {
http.Error(w, "only GET allowed", http.StatusMethodNotAllowed)
return
}
ups := h.b.GetSelfUpdateProgress()
json.NewEncoder(w).Encode(ups)
}
// serveDNSOSConfig serves the current system DNS configuration as a JSON object, if
// supported by the OS.
func (h *Handler) serveDNSOSConfig(w http.ResponseWriter, r *http.Request) {

View File

@ -9,7 +9,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
LDW github.com/coder/websocket/internal/errd from github.com/coder/websocket
LDW github.com/coder/websocket/internal/util from github.com/coder/websocket
LDW github.com/coder/websocket/internal/xsync from github.com/coder/websocket
W 💣 github.com/dblohm7/wingoes from github.com/dblohm7/wingoes/com+
W 💣 github.com/dblohm7/wingoes from tailscale.com/net/tshttpproxy+
W 💣 github.com/dblohm7/wingoes/com from tailscale.com/util/osdiag+
W 💣 github.com/dblohm7/wingoes/com/automation from tailscale.com/util/osdiag/internal/wsc
W github.com/dblohm7/wingoes/internal from github.com/dblohm7/wingoes/com
@ -28,8 +28,8 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/header+
DWI github.com/google/uuid from github.com/prometheus-community/pro-bing+
github.com/hdevalence/ed25519consensus from tailscale.com/clientupdate/distsign+
DI github.com/google/uuid from github.com/prometheus-community/pro-bing
github.com/hdevalence/ed25519consensus from tailscale.com/tka
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/netmon
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
github.com/klauspost/compress from github.com/klauspost/compress/zstd
@ -124,8 +124,6 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
tailscale.com/client/tailscale from tailscale.com/internal/client/tailscale
tailscale.com/client/tailscale/apitype from tailscale.com/client/local+
LDW tailscale.com/client/web from tailscale.com/ipn/ipnlocal
tailscale.com/clientupdate from tailscale.com/client/web+
LW tailscale.com/clientupdate/distsign from tailscale.com/clientupdate
tailscale.com/control/controlbase from tailscale.com/control/controlhttp+
tailscale.com/control/controlclient from tailscale.com/ipn/ipnext+
tailscale.com/control/controlhttp from tailscale.com/control/controlclient
@ -204,7 +202,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
tailscale.com/net/tlsdial/blockblame from tailscale.com/net/tlsdial
tailscale.com/net/tsaddr from tailscale.com/client/web+
tailscale.com/net/tsdial from tailscale.com/control/controlclient+
💣 tailscale.com/net/tshttpproxy from tailscale.com/clientupdate/distsign+
💣 tailscale.com/net/tshttpproxy from tailscale.com/control/controlclient+
tailscale.com/net/tstun from tailscale.com/tsd+
tailscale.com/net/udprelay/endpoint from tailscale.com/wgengine/magicsock
tailscale.com/net/udprelay/status from tailscale.com/client/local
@ -232,7 +230,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
tailscale.com/types/empty from tailscale.com/ipn+
tailscale.com/types/ipproto from tailscale.com/ipn+
tailscale.com/types/key from tailscale.com/client/local+
tailscale.com/types/lazy from tailscale.com/clientupdate+
tailscale.com/types/lazy from tailscale.com/hostinfo+
tailscale.com/types/logger from tailscale.com/appc+
tailscale.com/types/logid from tailscale.com/ipn/ipnlocal+
tailscale.com/types/mapx from tailscale.com/ipn/ipnext
@ -251,12 +249,12 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
tailscale.com/util/cibuild from tailscale.com/health
tailscale.com/util/clientmetric from tailscale.com/appc+
tailscale.com/util/cloudenv from tailscale.com/hostinfo+
tailscale.com/util/cmpver from tailscale.com/clientupdate+
LW tailscale.com/util/cmpver from tailscale.com/net/dns+
tailscale.com/util/ctxkey from tailscale.com/client/tailscale/apitype+
💣 tailscale.com/util/deephash from tailscale.com/ipn/ipnlocal+
LA 💣 tailscale.com/util/dirwalk from tailscale.com/metrics
tailscale.com/util/dnsname from tailscale.com/appc+
tailscale.com/util/eventbus from tailscale.com/ipn/localapi+
tailscale.com/util/eventbus from tailscale.com/client/local+
tailscale.com/util/execqueue from tailscale.com/appc+
tailscale.com/util/goroutines from tailscale.com/ipn/ipnlocal
tailscale.com/util/groupmember from tailscale.com/client/web+
@ -265,7 +263,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
tailscale.com/util/lineiter from tailscale.com/hostinfo+
tailscale.com/util/mak from tailscale.com/appc+
tailscale.com/util/multierr from tailscale.com/control/controlclient+
tailscale.com/util/must from tailscale.com/clientupdate/distsign+
tailscale.com/util/must from tailscale.com/logpolicy+
tailscale.com/util/nocasemaps from tailscale.com/types/ipproto
💣 tailscale.com/util/osdiag from tailscale.com/ipn/localapi
W 💣 tailscale.com/util/osdiag/internal/wsc from tailscale.com/util/osdiag
@ -291,8 +289,8 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
tailscale.com/util/truncate from tailscale.com/logtail
tailscale.com/util/usermetric from tailscale.com/health+
tailscale.com/util/vizerror from tailscale.com/tailcfg+
💣 tailscale.com/util/winutil from tailscale.com/clientupdate+
W 💣 tailscale.com/util/winutil/authenticode from tailscale.com/clientupdate+
💣 tailscale.com/util/winutil from tailscale.com/hostinfo+
W 💣 tailscale.com/util/winutil/authenticode from tailscale.com/util/osdiag
W 💣 tailscale.com/util/winutil/gp from tailscale.com/net/dns+
W tailscale.com/util/winutil/policy from tailscale.com/ipn/ipnlocal
W 💣 tailscale.com/util/winutil/winenv from tailscale.com/hostinfo+
@ -329,7 +327,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
LD golang.org/x/crypto/ssh from tailscale.com/ipn/ipnlocal
LD golang.org/x/crypto/ssh/internal/bcrypt_pbkdf from golang.org/x/crypto/ssh
golang.org/x/exp/constraints from github.com/dblohm7/wingoes/pe+
golang.org/x/exp/constraints from tailscale.com/tsweb/varz+
golang.org/x/exp/maps from tailscale.com/ipn/store/mem+
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
golang.org/x/net/dns/dnsmessage from net+
@ -364,9 +362,8 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
golang.org/x/text/unicode/bidi from golang.org/x/net/idna+
golang.org/x/text/unicode/norm from golang.org/x/net/idna
golang.org/x/time/rate from gvisor.dev/gvisor/pkg/log+
archive/tar from tailscale.com/clientupdate
bufio from compress/flate+
bytes from archive/tar+
bytes from bufio+
cmp from encoding/json+
compress/flate from compress/gzip+
compress/gzip from golang.org/x/net/http2+
@ -439,7 +436,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
crypto/x509 from crypto/tls+
DI crypto/x509/internal/macos from crypto/x509
crypto/x509/pkix from crypto/x509+
DWI database/sql/driver from github.com/google/uuid
DI database/sql/driver from github.com/google/uuid
W debug/dwarf from debug/pe
W debug/pe from github.com/dblohm7/wingoes/pe
embed from github.com/tailscale/web-client-prebuilt+
@ -452,11 +449,11 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
encoding/json from expvar+
encoding/pem from crypto/tls+
encoding/xml from github.com/tailscale/goupnp+
errors from archive/tar+
errors from bufio+
expvar from tailscale.com/health+
flag from tailscale.com/util/testenv
fmt from archive/tar+
hash from compress/zlib+
fmt from compress/flate+
hash from crypto+
W hash/adler32 from compress/zlib
hash/crc32 from compress/gzip+
hash/maphash from go4.org/mem
@ -473,7 +470,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
internal/filepathlite from os+
internal/fmtsort from fmt+
internal/goarch from crypto/internal/fips140deps/cpu+
internal/godebug from archive/tar+
internal/godebug from crypto/internal/fips140deps/godebug+
internal/godebugs from internal/godebug+
internal/goexperiment from hash/maphash+
internal/goos from crypto/x509+
@ -509,14 +506,14 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
internal/testlog from os
internal/trace/tracev2 from runtime+
internal/unsafeheader from internal/reflectlite+
io from archive/tar+
io/fs from archive/tar+
io from bufio+
io/fs from crypto/x509+
io/ioutil from github.com/digitalocean/go-smbios/smbios+
iter from bytes+
log from expvar+
log/internal from log
maps from archive/tar+
math from archive/tar+
maps from crypto/x509+
math from compress/flate+
math/big from crypto/dsa+
math/bits from bytes+
math/rand from github.com/fxamacker/cbor/v2+
@ -537,28 +534,28 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
net/url from crypto/x509+
os from crypto/internal/sysrand+
os/exec from github.com/godbus/dbus/v5+
os/user from archive/tar+
path from archive/tar+
path/filepath from archive/tar+
reflect from archive/tar+
os/user from github.com/godbus/dbus/v5+
path from debug/dwarf+
path/filepath from crypto/x509+
reflect from crypto/x509+
regexp from github.com/tailscale/goupnp/httpu+
regexp/syntax from regexp
runtime from archive/tar+
runtime from crypto/internal/fips140+
runtime/debug from github.com/coder/websocket/internal/xsync+
runtime/pprof from net/http/pprof+
runtime/trace from net/http/pprof
slices from archive/tar+
slices from crypto/tls+
sort from compress/flate+
strconv from archive/tar+
strings from archive/tar+
strconv from compress/flate+
strings from bufio+
W structs from internal/syscall/windows
sync from archive/tar+
sync from compress/flate+
sync/atomic from context+
syscall from archive/tar+
syscall from crypto/internal/sysrand+
text/tabwriter from runtime/pprof
LDW text/template from html/template
LDW text/template/parse from html/template+
time from archive/tar+
time from compress/gzip+
unicode from bytes+
unicode/utf16 from crypto/x509+
unicode/utf8 from bufio+

View File

@ -33,8 +33,9 @@ import (
"go4.org/mem"
"tailscale.com/client/local"
"tailscale.com/client/tailscale"
"tailscale.com/clientupdate"
"tailscale.com/cmd/testwrapper/flakytest"
"tailscale.com/feature"
_ "tailscale.com/feature/clientupdate"
"tailscale.com/hostinfo"
"tailscale.com/ipn"
"tailscale.com/net/tsaddr"
@ -1125,7 +1126,7 @@ func TestLogoutRemovesAllPeers(t *testing.T) {
}
func TestAutoUpdateDefaults(t *testing.T) {
if !clientupdate.CanAutoUpdate() {
if !feature.CanAutoUpdate() {
t.Skip("auto-updates not supported on this platform")
}
tstest.Shard(t)