diff --git a/ipn/ipn_clone.go b/ipn/ipn_clone.go index 8971b7b90..34b7bc5a7 100644 --- a/ipn/ipn_clone.go +++ b/ipn/ipn_clone.go @@ -53,6 +53,7 @@ var _PrefsCloneNeedsRegeneration = Prefs(struct { NoSNAT bool NetfilterMode preftype.NetfilterMode OperatorUser string + ProfileName string Persist *persist.Persist }{}) diff --git a/ipn/ipn_view.go b/ipn/ipn_view.go index c117148da..1cfa0eee9 100644 --- a/ipn/ipn_view.go +++ b/ipn/ipn_view.go @@ -86,6 +86,7 @@ func (v PrefsView) AdvertiseRoutes() views.IPPrefixSlice { 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) Persist() *persist.Persist { if v.ж.Persist == nil { return nil @@ -116,6 +117,7 @@ var _PrefsViewNeedsRegeneration = Prefs(struct { NoSNAT bool NetfilterMode preftype.NetfilterMode OperatorUser string + ProfileName string Persist *persist.Persist }{}) diff --git a/ipn/ipnlocal/profiles.go b/ipn/ipnlocal/profiles.go index 2d9c3e42d..e6fada02d 100644 --- a/ipn/ipnlocal/profiles.go +++ b/ipn/ipnlocal/profiles.go @@ -174,9 +174,13 @@ func (pm *profileManager) SetPrefs(prefsIn ipn.PrefsView) error { } cp.LocalUserID = pm.currentUserID } + if prefs.ProfileName() != "" { + cp.Name = prefs.ProfileName() + } else { + cp.Name = up.LoginName + } cp.UserProfile = newPersist.UserProfile cp.NodeID = newPersist.NodeID - cp.Name = up.LoginName pm.knownProfiles[cp.ID] = cp pm.currentProfile = cp if err := pm.writeKnownProfiles(); err != nil { diff --git a/ipn/prefs.go b/ipn/prefs.go index bde0d0b34..7ec112150 100644 --- a/ipn/prefs.go +++ b/ipn/prefs.go @@ -190,6 +190,11 @@ type Prefs struct { // operate tailscaled without being root or using sudo. OperatorUser string `json:",omitempty"` + // ProfileName is the desired name of the profile. If empty, then the users + // LoginName is used. It is only used for display purposes in the client UI + // and CLI. + ProfileName string `json:",omitempty"` + // 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. @@ -222,6 +227,7 @@ type MaskedPrefs struct { NoSNATSet bool `json:",omitempty"` NetfilterModeSet bool `json:",omitempty"` OperatorUserSet bool `json:",omitempty"` + ProfileNameSet bool `json:",omitempty"` } // ApplyEdits mutates p, assigning fields from m.Prefs for each MaskedPrefs @@ -406,7 +412,8 @@ func (p *Prefs) Equals(p2 *Prefs) bool { p.ForceDaemon == p2.ForceDaemon && compareIPNets(p.AdvertiseRoutes, p2.AdvertiseRoutes) && compareStrings(p.AdvertiseTags, p2.AdvertiseTags) && - p.Persist.Equals(p2.Persist) + p.Persist.Equals(p2.Persist) && + p.ProfileName == p2.ProfileName } func compareIPNets(a, b []netip.Prefix) bool { diff --git a/ipn/prefs_test.go b/ipn/prefs_test.go index 9e49f0cc0..0673cc15f 100644 --- a/ipn/prefs_test.go +++ b/ipn/prefs_test.go @@ -56,6 +56,7 @@ func TestPrefsEqual(t *testing.T) { "NoSNAT", "NetfilterMode", "OperatorUser", + "ProfileName", "Persist", } if have := fieldsOf(reflect.TypeOf(Prefs{})); !reflect.DeepEqual(have, prefsHandles) { @@ -272,6 +273,16 @@ func TestPrefsEqual(t *testing.T) { &Prefs{Persist: &persist.Persist{LoginName: "dave"}}, true, }, + { + &Prefs{ProfileName: "work"}, + &Prefs{ProfileName: "work"}, + true, + }, + { + &Prefs{ProfileName: "work"}, + &Prefs{ProfileName: "home"}, + false, + }, } for i, tt := range tests { got := tt.a.Equals(tt.b)