cmd/tailscale,ipn: add auto-update flags and prefs (#8861)
The flags are hidden for now. Adding propagation to tailscaled and persistence only. The prefs field is wrapped in a struct to allow for future expansion (like update schedule). Updates #6907 Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
This commit is contained in:
parent
055fdb235f
commit
34e3450734
|
@ -556,6 +556,10 @@ func TestPrefsFromUpArgs(t *testing.T) {
|
|||
NetfilterMode: preftype.NetfilterOn,
|
||||
CorpDNS: true,
|
||||
AllowSingleHosts: true,
|
||||
AutoUpdate: ipn.AutoUpdatePrefs{
|
||||
Check: true,
|
||||
Apply: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -569,6 +573,10 @@ func TestPrefsFromUpArgs(t *testing.T) {
|
|||
AllowSingleHosts: true,
|
||||
RouteAll: true,
|
||||
NetfilterMode: preftype.NetfilterOn,
|
||||
AutoUpdate: ipn.AutoUpdatePrefs{
|
||||
Check: true,
|
||||
Apply: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -584,6 +592,10 @@ func TestPrefsFromUpArgs(t *testing.T) {
|
|||
netip.MustParsePrefix("::/0"),
|
||||
},
|
||||
NetfilterMode: preftype.NetfilterOn,
|
||||
AutoUpdate: ipn.AutoUpdatePrefs{
|
||||
Check: true,
|
||||
Apply: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -670,6 +682,10 @@ func TestPrefsFromUpArgs(t *testing.T) {
|
|||
WantRunning: true,
|
||||
NetfilterMode: preftype.NetfilterNoDivert,
|
||||
NoSNAT: true,
|
||||
AutoUpdate: ipn.AutoUpdatePrefs{
|
||||
Check: true,
|
||||
Apply: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -683,6 +699,10 @@ func TestPrefsFromUpArgs(t *testing.T) {
|
|||
WantRunning: true,
|
||||
NetfilterMode: preftype.NetfilterOff,
|
||||
NoSNAT: true,
|
||||
AutoUpdate: ipn.AutoUpdatePrefs{
|
||||
Check: true,
|
||||
Apply: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -698,6 +718,10 @@ func TestPrefsFromUpArgs(t *testing.T) {
|
|||
AdvertiseRoutes: []netip.Prefix{
|
||||
netip.MustParsePrefix("fd7a:115c:a1e0:b1a::bb:10.0.0.0/112"),
|
||||
},
|
||||
AutoUpdate: ipn.AutoUpdatePrefs{
|
||||
Check: true,
|
||||
Apply: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"net/netip"
|
||||
|
||||
"github.com/peterbourgon/ff/v3/ffcli"
|
||||
"tailscale.com/clientupdate"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/net/tsaddr"
|
||||
|
@ -46,6 +47,8 @@ type setArgsT struct {
|
|||
acceptedRisks string
|
||||
profileName string
|
||||
forceDaemon bool
|
||||
updateCheck bool
|
||||
updateApply bool
|
||||
}
|
||||
|
||||
func newSetFlagSet(goos string, setArgs *setArgsT) *flag.FlagSet {
|
||||
|
@ -61,6 +64,8 @@ func newSetFlagSet(goos string, setArgs *setArgsT) *flag.FlagSet {
|
|||
setf.StringVar(&setArgs.hostname, "hostname", "", "hostname to use instead of the one provided by the OS")
|
||||
setf.StringVar(&setArgs.advertiseRoutes, "advertise-routes", "", "routes to advertise to other nodes (comma-separated, e.g. \"10.0.0.0/8,192.168.0.0/24\") or empty string to not advertise routes")
|
||||
setf.BoolVar(&setArgs.advertiseDefaultRoute, "advertise-exit-node", false, "offer to be an exit node for internet traffic for the tailnet")
|
||||
setf.BoolVar(&setArgs.updateCheck, "update-check", true, "HIDDEN: notify about available Tailscale updates")
|
||||
setf.BoolVar(&setArgs.updateApply, "auto-update", false, "HIDDEN: automatically update to the latest available version")
|
||||
if safesocket.GOOSUsesPeerCreds(goos) {
|
||||
setf.StringVar(&setArgs.opUser, "operator", "", "Unix username to allow to operate on tailscaled without sudo")
|
||||
}
|
||||
|
@ -99,6 +104,10 @@ func runSet(ctx context.Context, args []string) (retErr error) {
|
|||
Hostname: setArgs.hostname,
|
||||
OperatorUser: setArgs.opUser,
|
||||
ForceDaemon: setArgs.forceDaemon,
|
||||
AutoUpdate: ipn.AutoUpdatePrefs{
|
||||
Check: setArgs.updateCheck,
|
||||
Apply: setArgs.updateApply,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -143,6 +152,12 @@ func runSet(ctx context.Context, args []string) (retErr error) {
|
|||
return err
|
||||
}
|
||||
}
|
||||
if maskedPrefs.AutoUpdateSet {
|
||||
_, err := clientupdate.NewUpdater(clientupdate.Arguments{})
|
||||
if errors.Is(err, errors.ErrUnsupported) {
|
||||
return errors.New("automatic updates are not supported on this platform")
|
||||
}
|
||||
}
|
||||
checkPrefs := curPrefs.Clone()
|
||||
checkPrefs.ApplyEdits(maskedPrefs)
|
||||
if err := localClient.CheckPrefs(ctx, checkPrefs); err != nil {
|
||||
|
|
|
@ -97,6 +97,8 @@ func newUpFlagSet(goos string, upArgs *upArgsT, cmd string) *flag.FlagSet {
|
|||
}
|
||||
upf := newFlagSet(cmd)
|
||||
|
||||
// When adding new flags, prefer to put them under "tailscale set" instead
|
||||
// of here. Setting preferences via "tailscale up" is deprecated.
|
||||
upf.BoolVar(&upArgs.qr, "qr", false, "show QR code for login URLs")
|
||||
upf.StringVar(&upArgs.authKeyOrFile, "auth-key", "", `node authorization key; if it begins with "file:", then it's a path to a file containing the authkey`)
|
||||
|
||||
|
@ -712,6 +714,8 @@ func init() {
|
|||
addPrefFlagMapping("operator", "OperatorUser")
|
||||
addPrefFlagMapping("ssh", "RunSSH")
|
||||
addPrefFlagMapping("nickname", "ProfileName")
|
||||
addPrefFlagMapping("update-check", "AutoUpdate")
|
||||
addPrefFlagMapping("auto-update", "AutoUpdate")
|
||||
}
|
||||
|
||||
func addPrefFlagMapping(flagName string, prefNames ...string) {
|
||||
|
|
|
@ -51,6 +51,7 @@ var _PrefsCloneNeedsRegeneration = Prefs(struct {
|
|||
NetfilterMode preftype.NetfilterMode
|
||||
OperatorUser string
|
||||
ProfileName string
|
||||
AutoUpdate AutoUpdatePrefs
|
||||
Persist *persist.Persist
|
||||
}{})
|
||||
|
||||
|
|
|
@ -86,6 +86,7 @@ func (v PrefsView) NoSNAT() bool { return v.ж.NoSNAT }
|
|||
func (v PrefsView) NetfilterMode() preftype.NetfilterMode { return v.ж.NetfilterMode }
|
||||
func (v PrefsView) OperatorUser() string { return v.ж.OperatorUser }
|
||||
func (v PrefsView) ProfileName() string { return v.ж.ProfileName }
|
||||
func (v PrefsView) AutoUpdate() AutoUpdatePrefs { return v.ж.AutoUpdate }
|
||||
func (v PrefsView) Persist() persist.PersistView { return v.ж.Persist.View() }
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
|
@ -111,6 +112,7 @@ var _PrefsViewNeedsRegeneration = Prefs(struct {
|
|||
NetfilterMode preftype.NetfilterMode
|
||||
OperatorUser string
|
||||
ProfileName string
|
||||
AutoUpdate AutoUpdatePrefs
|
||||
Persist *persist.Persist
|
||||
}{})
|
||||
|
||||
|
|
|
@ -127,9 +127,10 @@ func (b *LocalBackend) handleC2NUpdate(w http.ResponseWriter, r *http.Request) {
|
|||
//
|
||||
// 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()
|
||||
_, err := clientupdate.NewUpdater(clientupdate.Arguments{})
|
||||
res := tailcfg.C2NUpdateResponse{
|
||||
Enabled: envknob.AllowsRemoteUpdate(),
|
||||
Enabled: envknob.AllowsRemoteUpdate() || prefs.Apply,
|
||||
Supported: err == nil && !version.IsMacSysExt(),
|
||||
}
|
||||
|
||||
|
|
41
ipn/prefs.go
41
ipn/prefs.go
|
@ -196,6 +196,10 @@ type Prefs struct {
|
|||
// and CLI.
|
||||
ProfileName string `json:",omitempty"`
|
||||
|
||||
// AutoUpdate sets the auto-update preferences for the node agent. See
|
||||
// AutoUpdatePrefs docs for more details.
|
||||
AutoUpdate AutoUpdatePrefs
|
||||
|
||||
// The Persist field is named 'Config' in the file for backward
|
||||
// compatibility with earlier versions.
|
||||
// TODO(apenwarr): We should move this out of here, it's not a pref.
|
||||
|
@ -204,6 +208,18 @@ type Prefs struct {
|
|||
Persist *persist.Persist `json:"Config"`
|
||||
}
|
||||
|
||||
// AutoUpdatePrefs are the auto update settings for the node agent.
|
||||
type AutoUpdatePrefs struct {
|
||||
// Check specifies whether background checks for updates are enabled. When
|
||||
// enabled, tailscaled will periodically check for available updates and
|
||||
// notify the user about them.
|
||||
Check bool
|
||||
// Apply specifies whether background auto-updates are enabled. When
|
||||
// enabled, tailscaled will apply available updates in the background.
|
||||
// Check must also be set when Apply is set.
|
||||
Apply bool
|
||||
}
|
||||
|
||||
// MaskedPrefs is a Prefs with an associated bitmask of which fields are set.
|
||||
type MaskedPrefs struct {
|
||||
Prefs
|
||||
|
@ -229,6 +245,7 @@ type MaskedPrefs struct {
|
|||
NetfilterModeSet bool `json:",omitempty"`
|
||||
OperatorUserSet bool `json:",omitempty"`
|
||||
ProfileNameSet bool `json:",omitempty"`
|
||||
AutoUpdateSet bool `json:",omitempty"`
|
||||
}
|
||||
|
||||
// ApplyEdits mutates p, assigning fields from m.Prefs for each MaskedPrefs
|
||||
|
@ -284,6 +301,12 @@ func (m *MaskedPrefs) Pretty() string {
|
|||
if v.Type().Elem().Kind() == reflect.String {
|
||||
return "%s=%q"
|
||||
}
|
||||
case reflect.Struct:
|
||||
return "%s=%+v"
|
||||
case reflect.Pointer:
|
||||
if v.Type().Elem().Kind() == reflect.Struct {
|
||||
return "%s=%+v"
|
||||
}
|
||||
}
|
||||
return "%s=%v"
|
||||
}
|
||||
|
@ -360,6 +383,7 @@ func (p *Prefs) pretty(goos string) string {
|
|||
if p.OperatorUser != "" {
|
||||
fmt.Fprintf(&sb, "op=%q ", p.OperatorUser)
|
||||
}
|
||||
sb.WriteString(p.AutoUpdate.Pretty())
|
||||
if p.Persist != nil {
|
||||
sb.WriteString(p.Persist.Pretty())
|
||||
} else {
|
||||
|
@ -414,7 +438,18 @@ func (p *Prefs) Equals(p2 *Prefs) bool {
|
|||
compareIPNets(p.AdvertiseRoutes, p2.AdvertiseRoutes) &&
|
||||
compareStrings(p.AdvertiseTags, p2.AdvertiseTags) &&
|
||||
p.Persist.Equals(p2.Persist) &&
|
||||
p.ProfileName == p2.ProfileName
|
||||
p.ProfileName == p2.ProfileName &&
|
||||
p.AutoUpdate == p2.AutoUpdate
|
||||
}
|
||||
|
||||
func (au AutoUpdatePrefs) Pretty() string {
|
||||
if au.Apply {
|
||||
return "update=on "
|
||||
}
|
||||
if au.Check {
|
||||
return "update=check "
|
||||
}
|
||||
return "update=off "
|
||||
}
|
||||
|
||||
func compareIPNets(a, b []netip.Prefix) bool {
|
||||
|
@ -459,6 +494,10 @@ func NewPrefs() *Prefs {
|
|||
CorpDNS: true,
|
||||
WantRunning: false,
|
||||
NetfilterMode: preftype.NetfilterOn,
|
||||
AutoUpdate: AutoUpdatePrefs{
|
||||
Check: true,
|
||||
Apply: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ func TestPrefsEqual(t *testing.T) {
|
|||
"NetfilterMode",
|
||||
"OperatorUser",
|
||||
"ProfileName",
|
||||
"AutoUpdate",
|
||||
"Persist",
|
||||
}
|
||||
if have := fieldsOf(reflect.TypeOf(Prefs{})); !reflect.DeepEqual(have, prefsHandles) {
|
||||
|
@ -288,6 +289,21 @@ func TestPrefsEqual(t *testing.T) {
|
|||
&Prefs{ProfileName: "home"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
&Prefs{AutoUpdate: AutoUpdatePrefs{Check: true, Apply: false}},
|
||||
&Prefs{AutoUpdate: AutoUpdatePrefs{Check: false, Apply: false}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
&Prefs{AutoUpdate: AutoUpdatePrefs{Check: true, Apply: true}},
|
||||
&Prefs{AutoUpdate: AutoUpdatePrefs{Check: true, Apply: false}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
&Prefs{AutoUpdate: AutoUpdatePrefs{Check: true, Apply: false}},
|
||||
&Prefs{AutoUpdate: AutoUpdatePrefs{Check: true, Apply: false}},
|
||||
true,
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
got := tt.a.Equals(tt.b)
|
||||
|
@ -372,22 +388,22 @@ func TestPrefsPretty(t *testing.T) {
|
|||
{
|
||||
Prefs{},
|
||||
"linux",
|
||||
"Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off Persist=nil}",
|
||||
"Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off Persist=nil}",
|
||||
},
|
||||
{
|
||||
Prefs{},
|
||||
"windows",
|
||||
"Prefs{ra=false mesh=false dns=false want=false Persist=nil}",
|
||||
"Prefs{ra=false mesh=false dns=false want=false update=off Persist=nil}",
|
||||
},
|
||||
{
|
||||
Prefs{ShieldsUp: true},
|
||||
"windows",
|
||||
"Prefs{ra=false mesh=false dns=false want=false shields=true Persist=nil}",
|
||||
"Prefs{ra=false mesh=false dns=false want=false shields=true update=off Persist=nil}",
|
||||
},
|
||||
{
|
||||
Prefs{AllowSingleHosts: true},
|
||||
"windows",
|
||||
"Prefs{ra=false dns=false want=false Persist=nil}",
|
||||
"Prefs{ra=false dns=false want=false update=off Persist=nil}",
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
|
@ -395,7 +411,7 @@ func TestPrefsPretty(t *testing.T) {
|
|||
AllowSingleHosts: true,
|
||||
},
|
||||
"windows",
|
||||
"Prefs{ra=false dns=false want=false notepad=true Persist=nil}",
|
||||
"Prefs{ra=false dns=false want=false notepad=true update=off Persist=nil}",
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
|
@ -404,7 +420,7 @@ func TestPrefsPretty(t *testing.T) {
|
|||
ForceDaemon: true, // server mode
|
||||
},
|
||||
"windows",
|
||||
"Prefs{ra=false dns=false want=true server=true Persist=nil}",
|
||||
"Prefs{ra=false dns=false want=true server=true update=off Persist=nil}",
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
|
@ -414,14 +430,14 @@ func TestPrefsPretty(t *testing.T) {
|
|||
AdvertiseTags: []string{"tag:foo", "tag:bar"},
|
||||
},
|
||||
"darwin",
|
||||
`Prefs{ra=false dns=false want=true tags=tag:foo,tag:bar url="http://localhost:1234" Persist=nil}`,
|
||||
`Prefs{ra=false dns=false want=true tags=tag:foo,tag:bar url="http://localhost:1234" update=off Persist=nil}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
Persist: &persist.Persist{},
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off Persist{lm=, o=, n= u=""}}`,
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off Persist{lm=, o=, n= u=""}}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
|
@ -430,21 +446,21 @@ func TestPrefsPretty(t *testing.T) {
|
|||
},
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off Persist{lm=, o=, n=[B1VKl] u=""}}`,
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off Persist{lm=, o=, n=[B1VKl] u=""}}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
ExitNodeIP: netip.MustParseAddr("1.2.3.4"),
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false exit=1.2.3.4 lan=false routes=[] nf=off Persist=nil}`,
|
||||
`Prefs{ra=false mesh=false dns=false want=false exit=1.2.3.4 lan=false routes=[] nf=off update=off Persist=nil}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
ExitNodeID: tailcfg.StableNodeID("myNodeABC"),
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false exit=myNodeABC lan=false routes=[] nf=off Persist=nil}`,
|
||||
`Prefs{ra=false mesh=false dns=false want=false exit=myNodeABC lan=false routes=[] nf=off update=off Persist=nil}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
|
@ -452,21 +468,41 @@ func TestPrefsPretty(t *testing.T) {
|
|||
ExitNodeAllowLANAccess: true,
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false exit=myNodeABC lan=true routes=[] nf=off Persist=nil}`,
|
||||
`Prefs{ra=false mesh=false dns=false want=false exit=myNodeABC lan=true routes=[] nf=off update=off Persist=nil}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
ExitNodeAllowLANAccess: true,
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off Persist=nil}`,
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off Persist=nil}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
Hostname: "foo",
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off host="foo" Persist=nil}`,
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off host="foo" update=off Persist=nil}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
AutoUpdate: AutoUpdatePrefs{
|
||||
Check: true,
|
||||
Apply: false,
|
||||
},
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=check Persist=nil}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
AutoUpdate: AutoUpdatePrefs{
|
||||
Check: true,
|
||||
Apply: true,
|
||||
},
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=on Persist=nil}`,
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
|
|
Loading…
Reference in New Issue