Integrates main's MDM configuration-profile feature and adapts it to the
Wails UI (this branch had already replaced the Fyne UI).
Conflict resolution:
- go.mod/go.sum: take main's deps; howett.net/plist pinned to v1.0.2-... (tidy)
- client/proto/daemon.pb.go: regenerated from the merged daemon.proto
- client/internal/peer/status.go: union of ipToKey (main) + sessionExpiresAt (HEAD)
- client/server/server.go: main's intent/liveness model (connectionGoroutineRunning,
clientRunning no longer cleared by the goroutine) + empty-PSK guard
- client/ui/client_ui.go, client/ui/profile.go: removed (dead Fyne UI)
MDM port (backend + tray):
- services/settings.go: expose MDMManagedFields plus a managedFields map keyed
by Config field names so the settings form can gate a control without
translating mdm.Key* names
- tray: gate Profiles / Exit Node menus on DisableProfiles / DisableNetworks via
GetFeatures, refreshed on the config_changed system event (replaces the legacy
2s poll); localized MDM policy-applied toast in all shipped locales
- client/proto/metadata.go: shared constants for the config_changed /
policy_applied event markers
PreSharedKey: GetConfig now returns preSharedKeySet (bool) instead of the masked
value; the settings form provides its own placeholder and sends a new key only
when the user types one.
* Initial scaffolding
* Applies MDM override
* Unit tests
* Helpers business logic
* Return error if trying to modify any config that is gated by MDM
* Add ManagedFields to returned config over GetConfig
* Adds initial 101 MDM policy business logic testing
* gRPC MDM changes
* MDM Name scoping for clarity
* Implements windows loading of MDM policy
* Adds missing WGPort config
* Cleanup setupKey to align to linear
* Align split tunnel code
* Adds some log
* Prefix every log with MDM
* Adds debug config cobra command
This can be useful for troubleshooting and checking config
now that its resolution is not trivial
defaults > config > env cars > CLI/UI > MDM
* Adds MDM 1m diff checker & reloader
* Adds also up/start after cancel
* Publishes event for UI to sync upon MDM changes
* Add events to resync UI to actual config
This also provide fixup for UI no aligning to changed config when coming from cli up with config flags.
* UI behavior conflicts relaxation
UI sends full config snapshot with all values. It doesn't
make sense to block it if the values are aligned with the
values constrained by the MDM policy. It's just simplier
to allow values that are compliant. (this goes for the CLI
as well at this point)
* Lock toggle Settngs
* Advanced Settings locking
* Fixup presharedkey
* Apply MDM locks
* Toggle gray in/out for Advanced Settings
* Adds support for disabling of Profiles and UpdateSettings feature flags
* Adds Gate Login as well when --disable-update-settings=true is given to service
This commit tries to settle things with an old PR-4237 which had relaxed
the case where the SetConfig returned an `Unavailable` code error.
Under this circumnstance the PR allowed the upFunc to just emit a warning and
progress further with the login gRPC. Since the login call is consuming
the --management-url coming from the `up` command, it might be possible
to abuse the "Unavailable" code to inject a management URL that is different
from the configured one even though the --disable-update-settings is set
to true (?)
* Evaluate disable-update-settings errors only when there's an actual override
* [UI] Fixup advanced Settings
* [UI] Fixup for preshared key
* [UI] Fixup for profile enable/disable toggle
We need to align the initial state to evaluate the delta in case.
The initial state has to be "true" since the profile starts visible.
Then we receive MDM and transition the cache bool value to the actual
MDM imposed state
* Enforces disable networks
* [UI] Aligns to "enable/disable once on change only"
* Fixup: MDM wins. always
* Removes --disable-advanced-settings
It was a typo in our meetings. the actual thing is --disable-update-settings
* [PROTO] Removes --disable-advanced-settings
* [UI] Removes --disable-advanced-settings
* Pins feat profile retrieval to notif event
* [UI] Fix for "hide" not working when propagating to parent with children
* Adds dep for reading plist files
* Introduces support for darwing plist loading
* Tests MDM config reload via ticker
* [PROVISIONING] ADMX/ADML/PS/bash scripts/templates
* CI fixes
- Add docstrings to `mdm_integration`
- refactor for cognitive complexity
- mod tidy
* Linting
* Add docstrings to `mdm_integration`
* nil,nil is no policy and no error. Allow it
* nil,nil is no policy and no error. Allow it
* exclude MDM profile adminstrated keys data from debug bundle
* Fixes Rosenpass left disable after MDM unlock
* Partial revert coderabbit added docstrings
* Renaming fix
* Avoid locking on clientRunning bool when the connection is aborted for whatever reason
We want to just signal this through the giveUpChan, we will manage the signal from
the waiter side and in case set it to false there. THis way we avoid locking,
which should allow the MDM down+wait_for_term_chan_signal_+up procedure
clientRunning is used to signal two different conditions here:
1. the initialization procedure is over (we have an engine)
2. the connection being up (or being attempted)
Probably these two functionalities should not alias, and the failure of the second condition
(because of any error) should just drive a reconnection (currently it's not happening,
and we silently go idle).
OR, mor probably, the two things are the SAME and there should not exist a case where
we did the "Up" initialization and connection attempt but we are not still attempting it.
* Moves test helper at te very bottom
* Addresses github comments
* No lock no copy
* Prevents engine not stopping within 10 secs from being paired by another instance
We instead juts SKIP updating the policy, so
1. the MDM ticker will kick in 1 minute time,
2. find the policy misaligned,
3. enter the onMDMPolicyChange,
4. find the s.clientRunning == true
(because it is set to false only in server cleanupConnection,
and not by s.actCancel())
5. call s.actCancel() again if not nil
6. immediately return from <-s.clientGiveUpChan
7. finally call s.restartEngineForMDMLocked()
* Since we ARE running there should be a config
If the config was cancelled midflight, connect will abort later on
* DisableAutoConnect should not stop a running connection.
DisableAutoConnect should just avoid the connection attempts *when the service starts*.
If we are started and we are up and running, DisableAutoConnect should not kick in.
Another PR will follow about this topic
* Removes unused vars
* Moves callback into Run method arg
* align comment to removal of DisableAutoConnect
DisableAutoConnect should just avoid the connection attempts *when the service starts*.
If we are started and we are up and running, DisableAutoConnect should not kick in
* Removes unused managed_fields data.
This was initially used to drive the UI but approach changed
to reload config/features upon notifications which makes this data redundant.
* Reorder stuff
* Unexport unrequired vars/functions
PoliciesEqual → policiesEqual
AllKeys → allKeys
* Adds list of MDM managed fields in the debug bundle
When the daemon is set to debug/trace, the GUI now automatically writes a
rotated gui-client.log in the user's config dir and the daemon's debug bundle
collects it. The UI learns the level both at startup (daemon already in debug)
and live, by piggybacking the existing SubscribeEvents stream: the daemon
publishes a marked log-level-changed SystemEvent (and a per-subscription
snapshot), which DaemonFeed routes to guilog.DebugLog instead of an OS toast.
The UI registers its log path via a new RegisterUILog RPC so the root daemon,
which can't resolve the user's config dir, knows where to find the file.
Manual --log-file (any value) disables the daemon-driven file logging.
Fix: client/ui SetLogLevel looked up proto.LogLevel_value with the lowercase
logrus name, which never matched the uppercase enum keys and silently fell back
to INFO — so trace/debug requests from the bundle flow had no effect.
The tray Selected an exit node with append=false, which the RouteSelector
treats as "drop the whole current selection" (default-on semantics), so
enabling an exit node also turned off every non-exit routed network the
user had on. Send append=true instead and let the daemon's SelectNetworks
handler deselect only the sibling exit nodes — matching the frontend's
toggleExitNode, which already used append=true.
Add a RouteSelector regression test covering the handler sequence.
The Wails notifications service connects to the D-Bus session bus in its
ServiceStartup, which Wails runs synchronously inside app.Run. daemonFeed.Watch
was started before app.Run, so the first daemon SubscribeEvents message (which
replays the cached available-update state) fanned out to the tray's update-state
listener and fired an OS notification before that startup ran. The notifier's
*dbus.Conn was still nil, so SendNotification nil-dereferenced deep in godbus and
the panic was fatal to the whole process (observed on Linux Mint).
Move daemonFeed.Watch into the ApplicationStarted hook so it runs after the
service-startup loop, and route every notification send through a new
safeSendNotification helper that recovers from a panic and logs it, so a broken
or unavailable notification bus degrades to a skipped toast instead of crashing.
Use the shared logrus logger alias and carry the JS origin in a dedicated
"ui" log field instead of inlining a [ui ...] tag in the message, keeping
frontend logs distinct from the Go-caller source.
The Wails v3 tray is a pure DBus StatusNotifierItem implementation and no
longer links libappindicator (a Fyne-era dependency). Drop the
libayatana-appindicator runtime and build deps, and move the rpm package
and dev-dependency docs onto the GTK4 / WebKitGTK 6.0 stack that the
default (non-gtk3) build actually links against.
Replace the doc comments above the empty no-op stubs (bindTrayClick,
startTrayTheme, noopSessionWatcher.Dismiss/Close) with short in-body
comments explaining why each is empty.
* [client] Surface session deadline rejections via SystemEvent and add timer arm debug logs
When sessionwatch.Watcher.Update rejects a deadline (pre-epoch, too far
in the future, or past the clock-skew tolerance) it silently zeroes the
status recorder, leaving the UI with no "expires in" row and no
indication of why. Publish a SystemEvent_ERROR on the AUTHENTICATION
channel so the rejection appears in the UI event feed and the user
knows re-login may be required.
Also add Debugf log lines in armTimerLocked so that warning and
final-warning timer fire-times are visible in logs without having to
add instrumentation after the fact.
https://claude.ai/code/session_01Y3bQoNgcVjTD4zDTvv7a8u
* [client] Remove verbose arm-timer debug logs from sessionwatch
The per-arm Debugf lines added noise on every deadline update.
Rejection logging already happens at the call site in engine_authsession.go;
the watcher itself needs no extra instrumentation.
https://claude.ai/code/session_01Y3bQoNgcVjTD4zDTvv7a8u
* [client] Leave userMessage empty on deadline-rejected event; use metadata key
Daemon-layer PublishEvent userMessage strings are not localized — the UI
reads metadata keys and builds its own locale-aware copy (same pattern
as the session-warning events in event.go). Drop the hardcoded English
sentence from the deadline-rejected event and instead surface the
rejection reason via a new MetaSessionDeadlineRejected metadata key so
the UI can detect and localize it.
https://claude.ai/code/session_01Y3bQoNgcVjTD4zDTvv7a8u
* [client] Revert silent deadline-rejected event; restore userMessage
MetaSessionDeadlineRejected had no UI consumer: the tray only does
metadata-driven localisation for MetaSessionWarning events; all other
SystemEvents display userMessage directly (tray_events.go). Leaving
userMessage empty made the rejection invisible to the user.
Restore the English userMessage so the generic event path shows
something, and remove the unused MetaSessionDeadlineRejected constant.
https://claude.ai/code/session_01Y3bQoNgcVjTD4zDTvv7a8u
* [client] Localize session deadline rejected notification via metadata key
Follow the same pattern as session-warning events: the daemon emits an
empty userMessage and puts the signal in a typed metadata key
(MetaSessionDeadlineRejected); the UI tray detects the key and builds a
locale-aware OS notification from i18n strings.
Changes:
- sessionwatch/event.go: add MetaSessionDeadlineRejected constant
- engine_authsession.go: empty userMessage, use the new metadata key
- ui/authsession/warning.go: re-export MetaDeadlineRejected for UI consumers
- ui/tray_events.go: gate on isDeadlineRejected alongside isSessionWarning;
new branch calls t.notify with localized title/body
- i18n locales (en/de/hu): add notify.sessionDeadlineRejected.{title,body}
https://claude.ai/code/session_01Y3bQoNgcVjTD4zDTvv7a8u
---------
Co-authored-by: Claude <noreply@anthropic.com>
logoutFromProfile failed hard when the management server returned NotFound
(peer already deleted from the dashboard), blocking both profile logout and
profile removal. Treat NotFound as success — the peer is already gone, so
deregistering it is already satisfied.
Also drop the user-side per-profile state file on logout. The account email is
sourced from <profile>.state.json (written by the CLI after SSO login), which
the root daemon can't reach, so logout left a stale email showing in the UI.
Connection.Logout now removes it from the UI process after a successful logout;
the next SSO login recreates it.
On minimal window managers (fluxbox et al, the in-process XEmbed-tray
path) the WM neither centers small windows nor restores their position
across a hide -> show round-trip, so the main, Settings, and dialog
windows opened in the top-left corner instead of centered.
These windows are created Hidden, so Wails' Linux/GTK4 backend skips its
post-Show centering pass (gated on !Hidden) and InitialPosition has no
effect on an unrealized window. Re-center from Go after Show, gated on
the minimal-WM environment via a recenterOnShow predicate (set to
xembedTrayAvailable on Linux, nil on macOS/Windows where the WM handles
placement). centerWhenReady polls from a background goroutine until the
move actually lands -- Center() moves via raw X11, which no-ops while the
GdkSurface is still nil and GTK4 realizes it asynchronously after Show().
Also reorder xembed_host_linux.go so the static helpers (xembedTrayAvailable,
goMenuItemClicked) sit at the end, after the constructor and methods.