Skip to content

Use webview in MacOS setup experience#33884

Merged
sgress454 merged 21 commits intomainfrom
sgress454/new-setup-experience
Oct 8, 2025
Merged

Use webview in MacOS setup experience#33884
sgress454 merged 21 commits intomainfrom
sgress454/new-setup-experience

Conversation

@sgress454
Copy link
Copy Markdown
Contributor

@sgress454 sgress454 commented Oct 6, 2025

Related issue: For #33111

Details

This PR updates the setup experience for MacOS to use a web view pointed at the device's "Setting up your device" page rather than using native MacOS UI elements, bringing it more in line with Linux and Windows setup experiences.

This covers only the new web UI for the setup experience progress, not the UI for the new case of blocking the device when a piece of software fails to install. I'll add that in a separate PR.

Checklist for submitter

If some of the following don't apply, delete the relevant line.

  • Changes file added for user-visible changes in changes/, orbit/changes/ or ee/fleetd-chrome/changes.
    See Changes files for more information.

Testing

Summary by CodeRabbit

  • New Features
    • macOS setup experience moved to a new web-based UI.
    • Automatic device token rotation during setup to keep sessions valid.
  • Bug Fixes
    • More reliable setup flow with improved dialog lifecycle and cleaner handoff to web content.
    • Dialog elements hidden/cleared appropriately when transitioning to the browser.
  • Documentation
    • Added guide and tool to simulate the macOS setup experience on a VM, with prerequisites and usage steps.

@sgress454 sgress454 requested a review from a team as a code owner October 6, 2025 19:07
@codecov
Copy link
Copy Markdown

codecov Bot commented Oct 6, 2025

Codecov Report

❌ Patch coverage is 41.52047% with 100 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.21%. Comparing base (e1ca48f) to head (7856795).
⚠️ Report is 54 commits behind head on main.

Files with missing lines Patch % Lines
orbit/pkg/setup_experience/setup_experience.go 0.00% 42 Missing ⚠️
orbit/cmd/orbit/orbit.go 0.00% 37 Missing ⚠️
orbit/pkg/token/readwriter.go 79.77% 14 Missing and 4 partials ⚠️
orbit/pkg/swiftdialog/run.go 0.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #33884      +/-   ##
==========================================
+ Coverage   64.13%   64.21%   +0.07%     
==========================================
  Files        2052     2054       +2     
  Lines      206102   206553     +451     
  Branches     6788     6788              
==========================================
+ Hits       132183   132628     +445     
+ Misses      63541    63510      -31     
- Partials    10378    10415      +37     
Flag Coverage Δ
backend 65.34% <41.52%> (+0.08%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sgress454 sgress454 marked this pull request as draft October 6, 2025 19:26
Comment thread orbit/cmd/orbit/orbit.go
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we're using the "My Device" page as the content for the MacOS setup experience, we need to ensure that we have a valid token for the duration of the setup experience. Previously, we only initialized the token and started rotating it if we detected that fleet desktop was enabled. Now, we'll always initialize it (just a single API call) and start rotating if either fleet desktop is enabled OR setup experience is starting (or both). To facilitate this, the token rotation code has been moved into ReadWriter itself, and callers can initiate it with a call to StartRotation(). This is an idempotent function that will start rotation once and return a function to request that the rotation stops; it will only actually stop when every caller has called StopRotation().

Comment thread orbit/cmd/orbit/orbit.go
UpdateRunner: updateRunner, RootDir: c.String("root-dir"), Interval: nudgeLaunchInterval,
}))
setupExperiencer := setupexperience.NewSetupExperiencer(orbitClient, c.String("root-dir"))
setupExperiencer := setupexperience.NewSetupExperiencer(orbitClient, deviceClient, c.String("root-dir"), trw)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deviceClient is needed in order to call BrowserDeviceURL.

Comment on lines +172 to +180
// Get the My Device URL.
browserURL := s.DeviceClient.BrowserDeviceURL(token)
// log out the url
log.Info().Msgf("setup experience: opening web content URL: %s", browserURL)
// Set the web content URL.
if err := s.sd.SetWebContent(browserURL + "?setup_only=1"); err != nil {
log.Info().Err(err).Msg("setting web content URL in setup experience UI")
return nil
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the token rotates, we'll automatically refresh the page here since the browserURL will have changed. So no extra code is needed to detect token rotation and manually refresh the page.

Comment on lines 460 to 462
func (s *SwiftDialog) HideIcon() error {
return s.sendCommand("icon", "hide")
return s.sendCommand("icon", "none")
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was wrong, but unused before so we didn't know it.

return os.Chmod(rw.Path, constant.DefaultWorldReadableFileMode)
}

func (rw *ReadWriter) StartRotation() func() {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly copied from the former implementation in orbit.go, except for the logic around stopping the rotation and making the durations configurable (mainly for testing purposes).

@sgress454
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 6, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 6, 2025

Walkthrough

Integrates token read/write and automated rotation into Orbit; updates the macOS setup experience to use a web UI via a device URL; extends the token package with rotation lifecycle and remote checks; tweaks SwiftDialog commands; and adds a CLI tool to prepare/enqueue macOS VM setup experience data via MySQL.

Changes

Cohort / File(s) Summary
Orbit CLI integration
orbit/cmd/orbit/orbit.go
Initializes token ReadWriter with a remote check, creates DeviceClient, wires remote update, validates/rotates token on startup, starts rotation watcher when fleet-desktop enabled, and passes deviceClient and trw into the setup experiencer; removes prior ad-hoc rotation goroutine path.
Setup Experience core
orbit/pkg/setup_experience/setup_experience.go
Adds OrbitClient and DeviceClient interfaces; SetupExperiencer now stores DeviceClient and trw, updated constructor NewSetupExperiencer(..., deviceClient, ..., trw); Run starts/stops token rotation and navigates SwiftDialog to device web URL (with setup_only=1), simplifying in-dialog state handling.
Token rotation infrastructure
orbit/pkg/token/readwriter.go, orbit/pkg/token/readwriter_test.go
ReadWriter gains rotation state and callbacks, constructor becomes NewReadWriter(path, checkTokenFunc), adds StartRotation() (returns stop func), remote/local check intervals, rotation watcher management, and integrates remoteUpdate on Write; tests updated to exercise rotation and new API.
SwiftDialog command tweaks
orbit/pkg/swiftdialog/run.go
Adds HideMessage() (sends message: none) and changes HideIcon() to send icon: none.
MDM setup tool (new)
tools/mdm/apple/setupexperience/main.go, tools/mdm/apple/setupexperience/README.md
New tool that prepares a macOS VM for the setup experience by connecting to MySQL, ensuring host/enrollment/profile rows, inserting required records, and enqueuing setup items; README documents usage and prerequisites.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User as macOS User
  participant Orbit as orbit (agent)
  participant TRW as Token ReadWriter
  participant DevClient as DeviceClient
  participant SwiftDlg as SwiftDialog
  participant WebUI as Setup Web UI

  rect rgba(230,240,255,0.5)
    note over Orbit,TRW: Init & token wiring
    Orbit->>TRW: NewReadWriter(path, checkTokenFunc)
    Orbit->>TRW: SetRemoteUpdateFunc(update via orbit client)
    Orbit->>DevClient: Init with Fleet URL/certs
    Orbit->>TRW: Read/validate token (rotate if needed)
  end

  rect rgba(240,255,240,0.5)
    note over Orbit,TRW: Rotation lifecycle
    Orbit->>TRW: StartRotation()
    note right of TRW: returns stop() to end rotation
  end

  rect rgba(255,250,230,0.5)
    note over Orbit,WebUI: Setup experience flow
    Orbit->>SwiftDlg: Prepare dialog (hide title/icon/message)
    Orbit->>TRW: Read token
    Orbit->>DevClient: BrowserDeviceURL(token)
    DevClient-->>Orbit: https://.../device?token=...&setup_only=1
    Orbit->>SwiftDlg: Load web content (device URL)
    User-->>WebUI: Complete browser UI steps
  end

  Orbit->>TRW: stop() (on completion/exit)
Loading
sequenceDiagram
  autonumber
  participant TRW as Token ReadWriter
  participant FS as Token File
  participant Server as Fleet Server

  rect rgba(245,245,255,0.6)
    note over TRW: Local rotation checks
    loop local check interval
      TRW->>FS: Stat/Read (mtime, contents)
      alt changed or expired
        TRW->>TRW: Rotate()
        TRW->>FS: Write(new token)
        TRW-->>Server: remoteUpdate(new token)
      else
        TRW-->>TRW: adjust next local check
      end
    end
  end

  rect rgba(255,245,240,0.6)
    note over TRW,Server: Remote validity checks
    loop remote check interval
      TRW->>Server: checkTokenFunc(token)
      alt invalid/unreachable
        TRW->>TRW: Rotate()
        TRW->>FS: Write(new token)
        TRW-->>Server: remoteUpdate(new token)
      else
        TRW-->>TRW: no-op
      end
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

customer-starchik, ~csa

Suggested reviewers

  • lukeheath
  • rachaelshaw
  • marko-lisica

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request description includes a related issue, detailed rationale, and the submitter checklist with testing items but omits several standard sections from the repository’s template such as Database migrations, New Fleet configuration settings, and the fleetd/orbit/Fleet Desktop compatibility checklist. Without these section headings or an explicit note that they are not applicable, the description does not fully adhere to the required template structure. Please include all standard PR description sections from the repository template—Database migrations, New Fleet configuration settings, and fleetd/orbit/Fleet Desktop—or explicitly mark them as not applicable, and complete the fleetd/orbit compatibility checklist for this change.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly captures the primary change of switching the MacOS setup experience to use a webview, reflecting the core update from native UI to web UI. It is concise, clear, and directly related to the main change in the PR.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sgress454/new-setup-experience

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

coderabbitai[bot]

This comment was marked as resolved.

@sgress454
Copy link
Copy Markdown
Contributor Author

@CodeRabbit review again

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 7, 2025

@sgress454: I'll review the changes again.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb2ef80 and 85e5adc.

📒 Files selected for processing (3)
  • orbit/pkg/setup_experience/setup_experience.go (7 hunks)
  • tools/mdm/apple/setupexperience/README.md (1 hunks)
  • tools/mdm/apple/setupexperience/main.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tools/mdm/apple/setupexperience/README.md
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

⚙️ CodeRabbit configuration file

When reviewing SQL queries that are added or modified, ensure that appropriate filtering criteria are applied—especially when a query is intended to return data for a specific entity (e.g., a single host). Check for missing WHERE clauses or incorrect filtering that could lead to incorrect or non-deterministic results (e.g., returning the first row instead of the correct one). Flag any queries that may return unintended results due to lack of precise scoping.

Files:

  • tools/mdm/apple/setupexperience/main.go
  • orbit/pkg/setup_experience/setup_experience.go
🧠 Learnings (1)
📚 Learning: 2025-10-03T18:16:11.429Z
Learnt from: MagnusHJensen
PR: fleetdm/fleet#33805
File: server/service/integration_mdm_test.go:1248-1251
Timestamp: 2025-10-03T18:16:11.429Z
Learning: In server/service/integration_mdm_test.go, the helper createAppleMobileHostThenEnrollMDM(platform string) is exclusively for iOS/iPadOS hosts (mobile). Do not flag macOS model/behavior issues based on changes within this helper; macOS provisioning uses different helpers such as createHostThenEnrollMDM.

Applied to files:

  • tools/mdm/apple/setupexperience/main.go
🔇 Additional comments (1)
orbit/pkg/setup_experience/setup_experience.go (1)

178-181: ...

Comment thread tools/mdm/apple/setupexperience/main.go Outdated
@sgress454 sgress454 marked this pull request as ready for review October 7, 2025 08:30
lucasmrod
lucasmrod previously approved these changes Oct 8, 2025
Copy link
Copy Markdown
Member

@lucasmrod lucasmrod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Left some nit comments.

uiSteps map[string]swiftdialog.ListItem
started bool
sd *swiftdialog.SwiftDialog
uiSteps map[string]swiftdialog.ListItem
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused.

if err != nil {
log.Error().Err(err).Msg("marshalling setup experience payload for logging")
} else {
log.Info().Msgf("setup experience payload: %s", string(payloadBytes))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably be log.Debug()?

// Get the My Device URL.
browserURL := s.DeviceClient.BrowserDeviceURL(token)
// log out the url
log.Info().Msgf("setup experience: opening web content URL: %s", browserURL)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, maybe just log.Debug().

log.Info().Msgf("setup experience: opening web content URL: %s", browserURL)
// Set the web content URL.
if err := s.sd.SetWebContent(browserURL + "?setup_only=1"); err != nil {
log.Info().Err(err).Msg("setting web content URL in setup experience UI")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: log.Error()


// Clear the dialog message.
if err := s.sd.HideMessage(); err != nil {
log.Info().Err(err).Msg("clearing message in setup experience UI")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

log.Error() same for the ones below.

7. Run this tool with the appropriate flags to set up the necessary database records, e.g.:

```bash
go run main.go -server-private-key=$(cat ~/path/to/private/key) -host-uuid="your-enrolled-host-uuid"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe instruct how to get the UUID of the VM?

@sgress454
Copy link
Copy Markdown
Contributor Author

@lucasmrod comments addressed, thanks!

@sgress454 sgress454 merged commit be7e004 into main Oct 8, 2025
53 of 54 checks passed
@sgress454 sgress454 deleted the sgress454/new-setup-experience branch October 8, 2025 16:51
sgress454 added a commit that referenced this pull request Oct 15, 2025
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** For #33111 

Fixes a possible race condition introduced in
#33884 which saw a test failure
[here](https://github.com/fleetdm/fleet/actions/runs/18454713591/job/52574112376#step:14:3571).

# Checklist for submitter

## Testing

- [ ] Added/updated automated tests
Existing test is sufficient, will circle back if it fails again.

- [X] QA'd all new/changed functionality manually
Verified that Fleet Desktop still opens My Device page correctly and
token rotation logs are still seen.

For unreleased bug fixes in a release candidate, one of:

- [X] Confirmed that the fix is not expected to adversely impact load
test results
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants