Skip to content

Hotfix/package manager install - #1927

Merged
danylo-babenko-flamingo merged 6 commits into
mainfrom
hotfix/package-manager-install
Aug 27, 2026
Merged

Hotfix/package manager install#1927
danylo-babenko-flamingo merged 6 commits into
mainfrom
hotfix/package-manager-install

Conversation

@danylo-babenko-flamingo

@danylo-babenko-flamingo danylo-babenko-flamingo commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Feature:

Make OpenFrame installable through package managers (Chocolatey, Homebrew, winget) by splitting installation from authentication into two independent steps.

A package cannot carry tenant parameters — Homebrew casks accept no install-time arguments at all, and a package that still requires values copied from the dashboard is no better than the existing one-liner. So the package installs a completely generic client, and enrollment happens afterwards as its own command:

step 1 — from the package manager, no parameters

choco install openframe -y | brew install --cask openframe | winget install openframe

step 2 — from the tenant dashboard (Devices → Add device)

openframe auth --serverUrl {tenant-name}.openframe.ai --initialKey --orgId --userId

This also unlocks deployment models the one-liner can't serve: baking an unenrolled client into a golden image (safe — no machine identity exists until enrollment), and MDM/RMM mass deployment where the configuration arrives separately. Installing with full parameters is unchanged and never needs auth.

Solution:

  • Parameterless install. InstallConfigParams::is_parameterless() (zero arguments — any partial set still fails validation, so a mangled command is caught rather than silently deferred) makes install skip build_and_save entirely. Binary copy, service registration and PATH wiring proceed as normal, and the run ends with a tenant-agnostic pointer to the dashboard plus a note that updates are managed by the platform.

  • Awaiting-auth gate in Service::run(), before Client::new(). The placement is load-bearing: the client reads initial_config.json during construction, so without the gate a parameterless install would fail in the constructor and crash-loop under SCM/launchd — a silent failure that looks like a broken install. Instead it logs one line and polls every 15s (AWAITING_AUTH_POLL_SECS) until a configuration appears. Release requires a file that both parses and carries a non-empty server_host, so a partially written file cannot release it.

  • New auth subcommand. Takes the same arguments as install (including optional --userId, --tag, --localMode) and runs doctor::run_auth: required arguments → admin → WebView2 → disk space → DNS/TCP/TLS/WebSocket probes against the supplied host. Nothing is written unless it passes; WebView2 is healed from that report, then the configuration is saved and the service restarted.

  • Two of those checks sit here deliberately. WebView2 is a chat-tool prerequisite, and tools only arrive after registration — so its natural home is the step immediately before provisioning, not the bare install. Disk space matters because the gap between install and auth can be months (golden image, pre-provisioned package) and tool provisioning starts as soon as auth succeeds.

  • The restart is not just about latency. Configuration is read once at startup, so re-running auth on an already-running client — typo'd host, tenant move, support asking for a re-run — would otherwise report success and change nothing until a reboot. Service::nudge_restart() is best-effort and silent; on failure the poll still picks the configuration up, which also covers config delivered by MDM file-drop rather than the CLI.

  • Diagnostics follow the step that needs them. Parameterless install runs run_preinstall_parameterless() — admin, bin dir, disk, the three data directories, service-config writability: exactly what installation touches. Argument, network and WebView2 checks move to auth, where the parameters finally exist. The full-parameter install keeps today's complete preinstall pass. All paths print the same report shape and share identical failure/warning handling, so a package manager's log always contains a named check table rather than a mid-operation error chain.

  • openframe alias. The package ID is openframe, so install creates that name beside openframe-client. On Windows it is a .cmd shim (@"%~dp0openframe-client.exe" %*) rather than a hard link — the updater replaces the binary by swapping its directory entry, which would leave a link serving the pre-update binary forever; %~dp0 resolves at run time, so the shim never goes stale. Unix uses a symlink, which already has that property. Removed on uninstall.

  • Supporting behavior. doctor reports a calm "Awaiting authentication" state on an unenrolled machine instead of a config-not-found failure; run (direct/dev mode) fails fast with a clear message rather than idling like the service.

Intentional, so reviewers don't flag them: re-running install on an enrolled machine still de-authenticates it — the internal reinstall uninstalls tools and clears app_support, while machine_id/client_secret survive so re-auth re-registers the same device. A deliberate reinstall is expected to go through both steps again. Package-manager upgrades are intentionally unsupported; agents update through the platform.

Validation: cargo fmt --check and clippy -D warnings clean; 140 tests, 135 passing (5 pre-existing ignored), including new coverage for the parameterless decision function and the gate's release condition. Behaviour needing real machines — service state while idling, launchd/SCM behaviour, the full install → auth → registration path — is tracked as post-review verification in possible_tickets/Working/CLIENT_PACKAGE_MANAGERS.

Review:

  1. The openframe alias was lost on an install run from the installed location. create_alias and add_to_windows_path sat inside the current_exe_path != install_path branch. Running the already-installed binary's install takes this path: the existing-installation check launches the internal uninstall (REINSTALL_ENV=1), which removes the alias, and then the copy branch is skipped because the paths are equal — so the alias and PATH entry were never restored, and the "run openframe auth …" instruction printed right afterwards failed with command-not-found. Both calls now run unconditionally after the copy branch; both are idempotent (create_alias removes any existing alias first, add_to_windows_path already no-ops when the directory is present).

  2. nudge_restart could leave the service stopped while auth reported success. If the stop succeeded and the start failed, both errors went to a file-only debug! while the CLI printed "Authentication saved" and exited 0. Nothing restarts an explicitly stopped service, so the device would never register — strictly worse than not restarting at all, since the awaiting-auth poll dies with the process. nudge_restart now returns Result and only starts what it actually stopped: a failed stop leaves the service running and returns Ok (the poll still applies the config), while a failed start propagates. The CLI then prints the platform-specific recovery command (sc start com.openframe.client / sudo launchctl kickstart -k system/com.openframe.client) and exits 1, so automation cannot mistake it for success. The configuration is saved either way, and re-running auth retries the restart.

  3. doctor reported any config read error as "awaiting authentication". The Err(_) arm swallowed permission-denied, locked files and I/O errors alongside a genuinely missing file, so openframe doctor printed "All checks passed" and exited 0 on an agent that cannot start. Only ErrorKind::NotFound now yields the informational awaiting-authentication result; every other error kind is a failure carrying the path and the OS error.

Comment thread clients/openframe-client/src/service.rs Outdated
Comment thread clients/openframe-client/src/service.rs Outdated
@danylo-babenko-flamingo
danylo-babenko-flamingo enabled auto-merge (squash) August 27, 2026 15:25
@danylo-babenko-flamingo
danylo-babenko-flamingo merged commit 816a6d8 into main Aug 27, 2026
7 checks passed
@danylo-babenko-flamingo
danylo-babenko-flamingo deleted the hotfix/package-manager-install branch August 27, 2026 15:32
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