mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-21 21:09:07 +02:00
[client] Add autostart preference marker and MDM disableAutostart key
Adds the autostartInitialized marker to the Wails UI preferences store so the one-time autostart default decision can persist per OS user, and a UI-only disableAutostart MDM policy key that suppresses the default and flows into GetConfigResponse.mDMManagedFields like disableAutoConnect.
This commit is contained in:
@@ -22,6 +22,7 @@ var allKeys = []string{
|
|||||||
KeyDisableMetricsCollection,
|
KeyDisableMetricsCollection,
|
||||||
KeyAllowServerSSH,
|
KeyAllowServerSSH,
|
||||||
KeyDisableAutoConnect,
|
KeyDisableAutoConnect,
|
||||||
|
KeyDisableAutostart,
|
||||||
KeyPreSharedKey,
|
KeyPreSharedKey,
|
||||||
KeyRosenpassEnabled,
|
KeyRosenpassEnabled,
|
||||||
KeyRosenpassPermissive,
|
KeyRosenpassPermissive,
|
||||||
|
|||||||
+14
-8
@@ -20,10 +20,10 @@ import (
|
|||||||
// names (lowerCamelCase) so the daemon can map a Policy key directly to a
|
// names (lowerCamelCase) so the daemon can map a Policy key directly to a
|
||||||
// configuration field.
|
// configuration field.
|
||||||
const (
|
const (
|
||||||
KeyManagementURL = "managementURL"
|
KeyManagementURL = "managementURL"
|
||||||
KeyDisableUpdateSettings = "disableUpdateSettings"
|
KeyDisableUpdateSettings = "disableUpdateSettings"
|
||||||
KeyDisableProfiles = "disableProfiles"
|
KeyDisableProfiles = "disableProfiles"
|
||||||
KeyDisableNetworks = "disableNetworks"
|
KeyDisableNetworks = "disableNetworks"
|
||||||
// KeyDisableAdvancedView gates the advanced-view section in the
|
// KeyDisableAdvancedView gates the advanced-view section in the
|
||||||
// upcoming UI revision. UI-only: NOT stored on Config, not
|
// upcoming UI revision. UI-only: NOT stored on Config, not
|
||||||
// applied by applyMDMPolicy, not rejectable via SetConfig. The
|
// applied by applyMDMPolicy, not rejectable via SetConfig. The
|
||||||
@@ -37,10 +37,16 @@ const (
|
|||||||
KeyDisableMetricsCollection = "disableMetricsCollection"
|
KeyDisableMetricsCollection = "disableMetricsCollection"
|
||||||
KeyAllowServerSSH = "allowServerSSH"
|
KeyAllowServerSSH = "allowServerSSH"
|
||||||
KeyDisableAutoConnect = "disableAutoConnect"
|
KeyDisableAutoConnect = "disableAutoConnect"
|
||||||
KeyPreSharedKey = "preSharedKey"
|
// KeyDisableAutostart suppresses the GUI's fresh-install
|
||||||
KeyRosenpassEnabled = "rosenpassEnabled"
|
// launch-on-login default and marks the Settings toggle as
|
||||||
KeyRosenpassPermissive = "rosenpassPermissive"
|
// MDM-managed. UI-only: NOT stored on Config and not applied by
|
||||||
KeyWireguardPort = "wireguardPort"
|
// applyMDMPolicy; the GUI reads it directly and it appears in
|
||||||
|
// GetConfigResponse.mDMManagedFields when set.
|
||||||
|
KeyDisableAutostart = "disableAutostart"
|
||||||
|
KeyPreSharedKey = "preSharedKey"
|
||||||
|
KeyRosenpassEnabled = "rosenpassEnabled"
|
||||||
|
KeyRosenpassPermissive = "rosenpassPermissive"
|
||||||
|
KeyWireguardPort = "wireguardPort"
|
||||||
|
|
||||||
// Split tunnel is modeled as a single conceptual policy with two
|
// Split tunnel is modeled as a single conceptual policy with two
|
||||||
// registry/plist values. KeySplitTunnelMode is the discriminator
|
// registry/plist values. KeySplitTunnelMode is the discriminator
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ type UIPreferences struct {
|
|||||||
Language i18n.LanguageCode `json:"language"`
|
Language i18n.LanguageCode `json:"language"`
|
||||||
ViewMode ViewMode `json:"viewMode"`
|
ViewMode ViewMode `json:"viewMode"`
|
||||||
OnboardingCompleted bool `json:"onboardingCompleted"`
|
OnboardingCompleted bool `json:"onboardingCompleted"`
|
||||||
|
// AutostartInitialized records that the one-time autostart default
|
||||||
|
// decision has run for this OS user. It only ever transitions to true
|
||||||
|
// and is never reset, so the default-on flow runs at most once, ever.
|
||||||
|
AutostartInitialized bool `json:"autostartInitialized"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LanguageValidator rejects SetLanguage inputs with no shipped bundle.
|
// LanguageValidator rejects SetLanguage inputs with no shipped bundle.
|
||||||
@@ -157,6 +161,27 @@ func (s *Store) SetOnboardingCompleted(done bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetAutostartInitialized persists the one-time autostart decision marker.
|
||||||
|
// No-op if unchanged.
|
||||||
|
func (s *Store) SetAutostartInitialized(done bool) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
if s.current.AutostartInitialized == done {
|
||||||
|
s.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
next := s.current
|
||||||
|
next.AutostartInitialized = done
|
||||||
|
if err := s.persistLocked(next); err != nil {
|
||||||
|
s.mu.Unlock()
|
||||||
|
return fmt.Errorf("persist preferences: %w", err)
|
||||||
|
}
|
||||||
|
s.current = next
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
s.broadcast(next)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetLanguage validates, persists, and broadcasts. No-op if unchanged.
|
// SetLanguage validates, persists, and broadcasts. No-op if unchanged.
|
||||||
func (s *Store) SetLanguage(lang i18n.LanguageCode) error {
|
func (s *Store) SetLanguage(lang i18n.LanguageCode) error {
|
||||||
if lang == "" {
|
if lang == "" {
|
||||||
|
|||||||
@@ -215,6 +215,29 @@ func TestStore_FileShapeIsJSON(t *testing.T) {
|
|||||||
assert.Equal(t, i18n.LanguageCode("hu"), parsed.Language)
|
assert.Equal(t, i18n.LanguageCode("hu"), parsed.Language)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStore_SetAutostartInitializedPersistsAcrossReload(t *testing.T) {
|
||||||
|
withTempConfigDir(t)
|
||||||
|
emitter := &recordingEmitter{}
|
||||||
|
s, err := NewStore(nil, emitter)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.False(t, s.Get().AutostartInitialized, "marker must default to false when no file is on disk")
|
||||||
|
|
||||||
|
require.NoError(t, s.SetAutostartInitialized(true))
|
||||||
|
assert.True(t, s.Get().AutostartInitialized, "Get should reflect the persisted marker")
|
||||||
|
require.Len(t, emitter.calledWith(EventPreferencesChanged), 1, "first marker write should broadcast")
|
||||||
|
|
||||||
|
// Re-setting the same value must be a no-op: no disk write, no broadcast.
|
||||||
|
require.NoError(t, s.SetAutostartInitialized(true))
|
||||||
|
assert.Len(t, emitter.calledWith(EventPreferencesChanged), 1, "idempotent marker write should not broadcast again")
|
||||||
|
|
||||||
|
// A fresh Store (new GUI launch) must see the marker so the autostart
|
||||||
|
// default decision never runs twice.
|
||||||
|
reloaded, err := NewStore(nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.True(t, reloaded.Get().AutostartInitialized, "marker must survive a reload from disk")
|
||||||
|
}
|
||||||
|
|
||||||
func TestStore_ErrUnsupportedSentinel(t *testing.T) {
|
func TestStore_ErrUnsupportedSentinel(t *testing.T) {
|
||||||
// Verifies callers can match on the sentinel error rather than parsing
|
// Verifies callers can match on the sentinel error rather than parsing
|
||||||
// strings — protects against accidental %v -> %w changes that would
|
// strings — protects against accidental %v -> %w changes that would
|
||||||
|
|||||||
@@ -20,11 +20,12 @@ type MDMFields struct {
|
|||||||
DisableServerRoutes bool `json:"disableServerRoutes"`
|
DisableServerRoutes bool `json:"disableServerRoutes"`
|
||||||
AllowServerSSH *bool `json:"allowServerSSH"`
|
AllowServerSSH *bool `json:"allowServerSSH"`
|
||||||
DisableAutoConnect bool `json:"disableAutoConnect"`
|
DisableAutoConnect bool `json:"disableAutoConnect"`
|
||||||
|
DisableAutostart bool `json:"disableAutostart"`
|
||||||
BlockInbound bool `json:"blockInbound"`
|
BlockInbound bool `json:"blockInbound"`
|
||||||
DisableMetricsCollection bool `json:"disableMetricsCollection"`
|
DisableMetricsCollection bool `json:"disableMetricsCollection"`
|
||||||
SplitTunnelMode bool `json:"splitTunnelMode"`
|
SplitTunnelMode bool `json:"splitTunnelMode"`
|
||||||
SplitTunnelApps bool `json:"splitTunnelApps"`
|
SplitTunnelApps bool `json:"splitTunnelApps"`
|
||||||
DisableAdvancedView bool `json:"disableAdvancedView"`
|
DisableAdvancedView bool `json:"disableAdvancedView"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Features struct {
|
type Features struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user