Skip to content

Commit cc95fc3

Browse files
committed
feat(updater): auto-install updates on launch by default
Add a persisted `updates.auto_install` setting (default on) so a restart silently picks up new versions: the startup check now downloads, installs, and relaunches into the update when enabled, and only surfaces the "update available" banner when the user has opted out. - settings.rs: get_auto_install_updates / set_auto_install_updates commands (default true when the key is absent) - lib.rs: register both commands - App.tsx: startup check honors the preference (auto-install + relaunch, or fall back to the banner) - UpdateChecker.tsx: "Install updates automatically" toggle in the Software Updates card, wired to the persisted setting Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
1 parent 6048999 commit cc95fc3

4 files changed

Lines changed: 92 additions & 4 deletions

File tree

apps/desktop/src-tauri/src/commands/settings.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,37 @@ pub async fn update_startup_settings(
122122
Ok(())
123123
}
124124

125+
/// App-settings key for the "auto-install updates on launch" switch.
126+
const AUTO_INSTALL_UPDATES_KEY: &str = "updates.auto_install";
127+
128+
/// Whether the app downloads + installs updates automatically on launch
129+
/// (then relaunches into the new version). Default **true** — a missing
130+
/// setting means auto-install is on.
131+
#[tauri::command]
132+
pub async fn get_auto_install_updates(app_state: State<'_, AppState>) -> Result<bool, String> {
133+
let stored = app_state
134+
.settings_repository
135+
.get(AUTO_INSTALL_UPDATES_KEY)
136+
.await
137+
.map_err(|e| e.to_string())?;
138+
Ok(stored.map(|v| v != "false").unwrap_or(true))
139+
}
140+
141+
/// Enable/disable automatic update installation on launch. Persisted.
142+
#[tauri::command]
143+
pub async fn set_auto_install_updates(
144+
enabled: bool,
145+
app_state: State<'_, AppState>,
146+
) -> Result<bool, String> {
147+
app_state
148+
.settings_repository
149+
.set(AUTO_INSTALL_UPDATES_KEY, &enabled.to_string())
150+
.await
151+
.map_err(|e| e.to_string())?;
152+
info!("[Settings] Auto-install updates set to {}", enabled);
153+
Ok(enabled)
154+
}
155+
125156
/// Check if app should start hidden (for auto-launch with --hidden flag)
126157
pub fn should_start_hidden() -> bool {
127158
let args: Vec<String> = std::env::args().collect();

apps/desktop/src-tauri/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -976,6 +976,8 @@ pub fn run() {
976976
// Startup settings commands
977977
commands::get_startup_settings,
978978
commands::update_startup_settings,
979+
commands::get_auto_install_updates,
980+
commands::set_auto_install_updates,
979981
])
980982
.build(tauri::generate_context!())
981983
.expect("error while building McpMux application")

apps/desktop/src/App.tsx

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,18 +98,36 @@ function AppContent() {
9898
const navigateTo = useNavigateTo();
9999
const [availableUpdate, setAvailableUpdate] = useState<{ version: string } | null>(null);
100100

101-
// Auto-check for updates on startup (silent check after 5 seconds)
101+
// Auto-check for updates on startup (silent check after 5 seconds).
102+
// When auto-install is enabled (the default), download + install + relaunch
103+
// into the new version — so a restart picks up updates with no clicks.
104+
// Otherwise just surface the dismissible banner for a manual install.
102105
useEffect(() => {
103106
const checkForUpdates = async () => {
104107
try {
105108
const { check } = await import('@tauri-apps/plugin-updater');
106109
const update = await check();
107-
if (update) {
108-
console.log(`[Auto-Update] Update available: ${update.version}`);
110+
if (!update) return;
111+
console.log(`[Auto-Update] Update available: ${update.version}`);
112+
113+
// Default to auto-install; honor the persisted opt-out.
114+
let autoInstall = true;
115+
try {
116+
autoInstall = await invoke<boolean>('get_auto_install_updates');
117+
} catch {
118+
/* setting unavailable → keep the auto-install default */
119+
}
120+
121+
if (autoInstall) {
122+
console.log('[Auto-Update] Auto-installing update and relaunching…');
123+
await update.downloadAndInstall();
124+
const { relaunch } = await import('@tauri-apps/plugin-process');
125+
await relaunch();
126+
} else {
109127
setAvailableUpdate({ version: update.version });
110128
}
111129
} catch (error) {
112-
console.error('[Auto-Update] Failed to check for updates:', error);
130+
console.error('[Auto-Update] Failed to check/install updates:', error);
113131
}
114132
};
115133

apps/desktop/src/features/settings/UpdateChecker.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
CardTitle,
99
CardDescription,
1010
CardContent,
11+
Switch,
1112
} from '@mcpmux/ui';
1213
import { Download, Loader2, CheckCircle, AlertCircle, RefreshCw, RotateCcw } from 'lucide-react';
1314
import { invoke } from '@tauri-apps/api/core';
@@ -28,6 +29,7 @@ export function UpdateChecker() {
2829
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
2930
const [currentVersion, setCurrentVersion] = useState<string>('');
3031
const [bundleVersionMismatch, setBundleVersionMismatch] = useState<string | null>(null);
32+
const [autoInstall, setAutoInstall] = useState<boolean | null>(null);
3133

3234
// Load current version on mount
3335
useState(() => {
@@ -36,6 +38,24 @@ export function UpdateChecker() {
3638
.catch((err) => console.error('Failed to get version:', err));
3739
});
3840

41+
// Load the auto-install preference (default on).
42+
useEffect(() => {
43+
invoke<boolean>('get_auto_install_updates')
44+
.then(setAutoInstall)
45+
.catch(() => setAutoInstall(true));
46+
}, []);
47+
48+
const handleToggleAutoInstall = async (next: boolean) => {
49+
const prev = autoInstall;
50+
setAutoInstall(next);
51+
try {
52+
await invoke('set_auto_install_updates', { enabled: next });
53+
} catch (err) {
54+
setAutoInstall(prev);
55+
setMessage({ type: 'error', text: `Failed to save setting: ${err}` });
56+
}
57+
};
58+
3959
// Check if the on-disk bundle version differs from the running version (Homebrew Cask upgrades)
4060
useEffect(() => {
4161
if (!currentVersion) return;
@@ -167,6 +187,23 @@ export function UpdateChecker() {
167187
</p>
168188
</div>
169189

190+
{/* Auto-install preference */}
191+
<div className="flex items-start justify-between gap-4 rounded-lg border p-3">
192+
<div className="space-y-0.5">
193+
<p className="text-sm font-medium">Install updates automatically</p>
194+
<p className="text-xs text-[rgb(var(--muted))]">
195+
Download and apply new versions on launch, then restart into the update. Turn off to
196+
review each update before installing.
197+
</p>
198+
</div>
199+
<Switch
200+
checked={autoInstall ?? true}
201+
disabled={autoInstall === null}
202+
onCheckedChange={handleToggleAutoInstall}
203+
data-testid="auto-install-updates-toggle"
204+
/>
205+
</div>
206+
170207
{/* Bundle version mismatch (e.g., after brew upgrade) */}
171208
{bundleVersionMismatch && (
172209
<div

0 commit comments

Comments
 (0)