feat: add option to disable automatic update downloads (#9872)

Signed-off-by: Douwe Osinga <douwe@sidewalklabs.com>
Co-authored-by: Crazyop757 <83007860+Crazyop757@users.noreply.github.com>
Co-authored-by: Douwe Osinga <douwe@sidewalklabs.com>
This commit is contained in:
Michael Neale
2026-06-21 08:41:30 +10:00
committed by GitHub
parent 6c2ec554de
commit d2a921a75d
13 changed files with 289 additions and 17 deletions
@@ -5,6 +5,28 @@ import { errorMessage } from '../../../utils/conversionUtils';
import { defineMessages, useIntl } from '../../../i18n';
const i18n = defineMessages({
disableAutoDownload: {
id: 'updateSection.disableAutoDownload',
defaultMessage: 'Disable automatic update downloads',
},
disableAutoDownloadDesc: {
id: 'updateSection.disableAutoDownloadDesc',
defaultMessage:
'When enabled, Goose will notify you of new versions but will not download them automatically.',
},
autoDownloadDisabledByEnv: {
id: 'updateSection.autoDownloadDisabledByEnv',
defaultMessage:
'Automatic downloads are disabled via the GOOSE_DISABLE_AUTO_DOWNLOAD environment variable.',
},
downloadNow: {
id: 'updateSection.downloadNow',
defaultMessage: 'Download Now',
},
autoDownloadDisabledNote: {
id: 'updateSection.autoDownloadDisabledNote',
defaultMessage: 'Automatic download is disabled. Click "Download Now" to download manually.',
},
loading: {
id: 'updateSection.loading',
defaultMessage: 'Loading...',
@@ -116,6 +138,8 @@ export default function UpdateSection() {
});
const [progress, setProgress] = useState<number>(0);
const [isUsingGitHubFallback, setIsUsingGitHubFallback] = useState<boolean>(false);
const [disableAutoDownload, setDisableAutoDownload] = useState<boolean>(false);
const [autoDownloadForcedByEnv, setAutoDownloadForcedByEnv] = useState<boolean>(false);
const progressTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
const lastProgressRef = React.useRef<number>(0); // Track last progress to prevent backward jumps
@@ -140,6 +164,16 @@ export default function UpdateSection() {
setIsUsingGitHubFallback(isGitHub);
});
window.electron.getSetting('disableAutoDownload').then((stored) => {
setDisableAutoDownload(!!stored);
});
window.electron.getAutoDownloadDisabled().then((effective) => {
window.electron.getSetting('disableAutoDownload').then((stored) => {
// If effective is true but user setting is false, env var is forcing it
setAutoDownloadForcedByEnv(effective && !stored);
});
});
// Listen for updater events
window.electron.onUpdaterEvent((event) => {
@@ -249,6 +283,30 @@ export default function UpdateSection() {
window.electron.installUpdate();
};
const downloadUpdate = async () => {
setUpdateStatus('downloading');
setProgress(0);
lastProgressRef.current = 0;
try {
const result = await window.electron.downloadUpdate();
if (result.error) {
throw new Error(result.error);
}
} catch (error) {
setUpdateInfo((prev) => ({
...prev,
error: errorMessage(error, 'Failed to download update'),
}));
setUpdateStatus('error');
setTimeout(() => setUpdateStatus('idle'), 5000);
}
};
const toggleAutoDownload = async (disabled: boolean) => {
setDisableAutoDownload(disabled);
await window.electron.setSetting('disableAutoDownload', disabled);
};
const getStatusMessage = () => {
switch (updateStatus) {
case 'checking':
@@ -287,6 +345,8 @@ export default function UpdateSection() {
}
};
const autoDownloadEffectivelyDisabled = disableAutoDownload || autoDownloadForcedByEnv;
return (
<div>
<div className="text-sm text-text-secondary mb-4 flex items-center gap-2">
@@ -315,6 +375,12 @@ export default function UpdateSection() {
{intl.formatMessage(i18n.checkForUpdates)}
</Button>
{updateInfo.isUpdateAvailable && updateStatus === 'idle' && autoDownloadEffectivelyDisabled && (
<Button onClick={downloadUpdate} variant="secondary" size="sm">
{intl.formatMessage(i18n.downloadNow)}
</Button>
)}
{updateStatus === 'ready' && (
<Button onClick={installUpdate} variant="default" size="sm">
{intl.formatMessage(i18n.installAndRestart)}
@@ -347,15 +413,23 @@ export default function UpdateSection() {
{/* Update information */}
{updateInfo.isUpdateAvailable && updateStatus === 'idle' && (
<div className="text-xs text-text-secondary mt-4 space-y-1">
<p>{intl.formatMessage(i18n.autoDownload)}</p>
{isUsingGitHubFallback ? (
{autoDownloadEffectivelyDisabled ? (
<p className="text-xs text-amber-600">
{intl.formatMessage(i18n.manualInstallNote)}
{intl.formatMessage(i18n.autoDownloadDisabledNote)}
</p>
) : (
<p className="text-xs text-green-600">
{intl.formatMessage(i18n.autoInstallNote)}
</p>
<>
<p>{intl.formatMessage(i18n.autoDownload)}</p>
{isUsingGitHubFallback ? (
<p className="text-xs text-amber-600">
{intl.formatMessage(i18n.manualInstallNote)}
</p>
) : (
<p className="text-xs text-green-600">
{intl.formatMessage(i18n.autoInstallNote)}
</p>
)}
</>
)}
</div>
)}
@@ -384,6 +458,32 @@ export default function UpdateSection() {
</div>
)}
</div>
{/* Auto-download toggle */}
<div className="mt-6 pt-4 border-t border-borderSubtle">
{autoDownloadForcedByEnv ? (
<p className="text-xs text-amber-600">
{intl.formatMessage(i18n.autoDownloadDisabledByEnv)}
</p>
) : (
<label className="flex items-start gap-3 cursor-pointer group">
<input
type="checkbox"
className="mt-0.5 cursor-pointer accent-bgApp"
checked={disableAutoDownload}
onChange={(e) => toggleAutoDownload(e.target.checked)}
/>
<div>
<p className="text-sm text-text-primary group-hover:text-text-primary">
{intl.formatMessage(i18n.disableAutoDownload)}
</p>
<p className="text-xs text-text-secondary mt-0.5">
{intl.formatMessage(i18n.disableAutoDownloadDesc)}
</p>
</div>
</label>
)}
</div>
</div>
);
}
+15
View File
@@ -4760,6 +4760,12 @@
"updateSection.autoDownload": {
"defaultMessage": "Update will be downloaded automatically in the background."
},
"updateSection.autoDownloadDisabledByEnv": {
"defaultMessage": "Automatic downloads are disabled via the GOOSE_DISABLE_AUTO_DOWNLOAD environment variable."
},
"updateSection.autoDownloadDisabledNote": {
"defaultMessage": "Automatic download is disabled. Click \"Download Now\" to download manually."
},
"updateSection.autoInstallNote": {
"defaultMessage": "The update will be installed automatically when you quit the app."
},
@@ -4772,6 +4778,15 @@
"updateSection.currentVersion": {
"defaultMessage": "Current version"
},
"updateSection.disableAutoDownload": {
"defaultMessage": "Disable automatic update downloads"
},
"updateSection.disableAutoDownloadDesc": {
"defaultMessage": "When enabled, Goose will notify you of new versions but will not download them automatically."
},
"updateSection.downloadNow": {
"defaultMessage": "Download Now"
},
"updateSection.downloadReady": {
"defaultMessage": "Update downloaded and ready to install!"
},
+15
View File
@@ -4760,6 +4760,12 @@
"updateSection.autoDownload": {
"defaultMessage": "La actualización se descargará automáticamente en segundo plano."
},
"updateSection.autoDownloadDisabledByEnv": {
"defaultMessage": "Las descargas automáticas están desactivadas mediante la variable de entorno GOOSE_DISABLE_AUTO_DOWNLOAD."
},
"updateSection.autoDownloadDisabledNote": {
"defaultMessage": "La descarga automática está desactivada. Haz clic en «Descargar ahora» para descargar manualmente."
},
"updateSection.autoInstallNote": {
"defaultMessage": "La actualización se instalará automáticamente cuando cierres la app."
},
@@ -4772,6 +4778,15 @@
"updateSection.currentVersion": {
"defaultMessage": "Versión actual"
},
"updateSection.disableAutoDownload": {
"defaultMessage": "Desactivar la descarga automática de actualizaciones"
},
"updateSection.disableAutoDownloadDesc": {
"defaultMessage": "Cuando está activado, Goose te avisará de nuevas versiones pero no las descargará automáticamente."
},
"updateSection.downloadNow": {
"defaultMessage": "Descargar ahora"
},
"updateSection.downloadReady": {
"defaultMessage": "¡Actualización descargada y lista para instalar!"
},
+15
View File
@@ -4760,6 +4760,12 @@
"updateSection.autoDownload": {
"defaultMessage": "अपडेट बैकग्राउंड में अपने आप डाउनलोड हो जाएगा."
},
"updateSection.autoDownloadDisabledByEnv": {
"defaultMessage": "स्वचालित डाउनलोड GOOSE_DISABLE_AUTO_DOWNLOAD पर्यावरण चर के माध्यम से अक्षम हैं।"
},
"updateSection.autoDownloadDisabledNote": {
"defaultMessage": "स्वचालित डाउनलोड अक्षम है। मैन्युअल रूप से डाउनलोड करने के लिए \"अभी डाउनलोड करें\" पर क्लिक करें।"
},
"updateSection.autoInstallNote": {
"defaultMessage": "जब आप ऐप छोड़ देंगे तो अपडेट अपने आप इंस्टॉल हो जाएगा।"
},
@@ -4772,6 +4778,15 @@
"updateSection.currentVersion": {
"defaultMessage": "वर्तमान संस्करण"
},
"updateSection.disableAutoDownload": {
"defaultMessage": "स्वचालित अपडेट डाउनलोड अक्षम करें"
},
"updateSection.disableAutoDownloadDesc": {
"defaultMessage": "सक्षम होने पर, Goose आपको नई संस्करणों की सूचना देगा लेकिन उन्हें स्वचालित रूप से डाउनलोड नहीं करेगा।"
},
"updateSection.downloadNow": {
"defaultMessage": "अभी डाउनलोड करें"
},
"updateSection.downloadReady": {
"defaultMessage": "अपडेट डाउनलोड हो गया है और इंस्टॉल करने के लिए तैयार है!"
},
+15
View File
@@ -4760,6 +4760,12 @@
"updateSection.autoDownload": {
"defaultMessage": "アップデートはバックグラウンドで自動的にダウンロードされます。"
},
"updateSection.autoDownloadDisabledByEnv": {
"defaultMessage": "自動ダウンロードは GOOSE_DISABLE_AUTO_DOWNLOAD 環境変数によって無効になっています。"
},
"updateSection.autoDownloadDisabledNote": {
"defaultMessage": "自動ダウンロードは無効です。手動でダウンロードするには「今すぐダウンロード」をクリックしてください。"
},
"updateSection.autoInstallNote": {
"defaultMessage": "アプリを終了すると、アップデートが自動的にインストールされます。"
},
@@ -4772,6 +4778,15 @@
"updateSection.currentVersion": {
"defaultMessage": "現在のバージョン"
},
"updateSection.disableAutoDownload": {
"defaultMessage": "アップデートの自動ダウンロードを無効にする"
},
"updateSection.disableAutoDownloadDesc": {
"defaultMessage": "有効にすると、Goose は新しいバージョンを通知しますが、自動的にはダウンロードしません。"
},
"updateSection.downloadNow": {
"defaultMessage": "今すぐダウンロード"
},
"updateSection.downloadReady": {
"defaultMessage": "アップデートのダウンロードが完了し、インストールの準備ができました!"
},
+15
View File
@@ -4760,6 +4760,12 @@
"updateSection.autoDownload": {
"defaultMessage": "업데이트는 백그라운드에서 자동으로 다운로드됩니다."
},
"updateSection.autoDownloadDisabledByEnv": {
"defaultMessage": "자동 다운로드는 GOOSE_DISABLE_AUTO_DOWNLOAD 환경 변수를 통해 비활성화되어 있습니다."
},
"updateSection.autoDownloadDisabledNote": {
"defaultMessage": "자동 다운로드가 비활성화되어 있습니다. 수동으로 다운로드하려면 \"지금 다운로드\"를 클릭하세요."
},
"updateSection.autoInstallNote": {
"defaultMessage": "앱을 종료하면 업데이트가 자동으로 설치됩니다."
},
@@ -4772,6 +4778,15 @@
"updateSection.currentVersion": {
"defaultMessage": "현재 버전"
},
"updateSection.disableAutoDownload": {
"defaultMessage": "자동 업데이트 다운로드 비활성화"
},
"updateSection.disableAutoDownloadDesc": {
"defaultMessage": "활성화하면 Goose가 새 버전을 알려주지만 자동으로 다운로드하지는 않습니다."
},
"updateSection.downloadNow": {
"defaultMessage": "지금 다운로드"
},
"updateSection.downloadReady": {
"defaultMessage": "업데이트가 다운로드되었으며 설치 준비가 완료되었습니다!"
},
+15
View File
@@ -4760,6 +4760,12 @@
"updateSection.autoDownload": {
"defaultMessage": "Обновление будет автоматически скачано в фоновом режиме."
},
"updateSection.autoDownloadDisabledByEnv": {
"defaultMessage": "Автоматическая загрузка отключена через переменную окружения GOOSE_DISABLE_AUTO_DOWNLOAD."
},
"updateSection.autoDownloadDisabledNote": {
"defaultMessage": "Автоматическая загрузка отключена. Нажмите «Скачать сейчас», чтобы загрузить вручную."
},
"updateSection.autoInstallNote": {
"defaultMessage": "Обновление будет установлено автоматически при выходе из приложения."
},
@@ -4772,6 +4778,15 @@
"updateSection.currentVersion": {
"defaultMessage": "Текущая версия"
},
"updateSection.disableAutoDownload": {
"defaultMessage": "Отключить автоматическую загрузку обновлений"
},
"updateSection.disableAutoDownloadDesc": {
"defaultMessage": "Когда включено, Goose будет уведомлять о новых версиях, но не будет загружать их автоматически."
},
"updateSection.downloadNow": {
"defaultMessage": "Скачать сейчас"
},
"updateSection.downloadReady": {
"defaultMessage": "Обновление скачано и готово к установке!"
},
+15
View File
@@ -4760,6 +4760,12 @@
"updateSection.autoDownload": {
"defaultMessage": "Güncelleme arka planda otomatik olarak indirilecektir."
},
"updateSection.autoDownloadDisabledByEnv": {
"defaultMessage": "Otomatik indirmeler GOOSE_DISABLE_AUTO_DOWNLOAD ortam değişkeni aracılığıyla devre dışı bırakılmıştır."
},
"updateSection.autoDownloadDisabledNote": {
"defaultMessage": "Otomatik indirme devre dışı. Manuel olarak indirmek için \"Şimdi İndir\"e tıklayın."
},
"updateSection.autoInstallNote": {
"defaultMessage": "Uygulamadan çıktığınızda güncelleme otomatik olarak yüklenecektir."
},
@@ -4772,6 +4778,15 @@
"updateSection.currentVersion": {
"defaultMessage": "Güncel sürüm"
},
"updateSection.disableAutoDownload": {
"defaultMessage": "Otomatik güncelleme indirmelerini devre dışı bırak"
},
"updateSection.disableAutoDownloadDesc": {
"defaultMessage": "Etkinleştirildiğinde, Goose yeni sürümleri bildirir ancak bunları otomatik olarak indirmez."
},
"updateSection.downloadNow": {
"defaultMessage": "Şimdi İndir"
},
"updateSection.downloadReady": {
"defaultMessage": "Güncelleme indirildi ve kuruluma hazır!"
},
+15
View File
@@ -4760,6 +4760,12 @@
"updateSection.autoDownload": {
"defaultMessage": "更新将在后台自动下载。"
},
"updateSection.autoDownloadDisabledByEnv": {
"defaultMessage": "自动下载已通过 GOOSE_DISABLE_AUTO_DOWNLOAD 环境变量禁用。"
},
"updateSection.autoDownloadDisabledNote": {
"defaultMessage": "自动下载已禁用。点击\"立即下载\"以手动下载。"
},
"updateSection.autoInstallNote": {
"defaultMessage": "退出应用时将自动安装更新。"
},
@@ -4772,6 +4778,15 @@
"updateSection.currentVersion": {
"defaultMessage": "当前版本"
},
"updateSection.disableAutoDownload": {
"defaultMessage": "禁用自动更新下载"
},
"updateSection.disableAutoDownloadDesc": {
"defaultMessage": "启用后,Goose 会通知你有新版本,但不会自动下载。"
},
"updateSection.downloadNow": {
"defaultMessage": "立即下载"
},
"updateSection.downloadReady": {
"defaultMessage": "更新已下载,可以安装!"
},
+10
View File
@@ -41,6 +41,7 @@ import windowStateKeeper from 'electron-window-state';
import {
getUpdateAvailable,
registerUpdateIpcHandlers,
setAutoDownloadDisabled,
setTrayRef,
setupAutoUpdater,
updateTrayMenu,
@@ -1736,6 +1737,7 @@ const validSettingKeys: Set<string> = new Set([
'showPricing',
'sessionSharing',
'seenAnnouncementIds',
'disableAutoDownload',
]);
ipcMain.handle('set-setting', (_event, key: SettingKey, value: unknown) => {
@@ -1763,6 +1765,10 @@ ipcMain.handle('set-setting', (_event, key: SettingKey, value: unknown) => {
if (key === 'keyboardShortcuts') {
registerGlobalShortcuts();
}
if (key === 'disableAutoDownload') {
setAutoDownloadDisabled(value as boolean);
}
});
ipcMain.handle('get-secret-key', () => {
@@ -2301,6 +2307,10 @@ async function appMain() {
if (shouldSetupUpdater()) {
log.info('Setting up auto-updater after window creation...');
try {
const settings = getSettings();
if (settings.disableAutoDownload) {
setAutoDownloadDisabled(true);
}
setupAutoUpdater();
} catch (error) {
log.error('Error setting up auto-updater:', error);
+4
View File
@@ -181,6 +181,7 @@ type ElectronAPI = {
onUpdaterEvent: (callback: (event: UpdaterEvent) => void) => void;
getUpdateState: () => Promise<{ updateAvailable: boolean; latestVersion?: string } | null>;
isUsingGitHubFallback: () => Promise<boolean>;
getAutoDownloadDisabled: () => Promise<boolean>;
// Recipe warning functions
closeWindow: () => void;
hasAcceptedRecipeBefore: (recipe: Recipe) => Promise<boolean>;
@@ -338,6 +339,9 @@ const electronAPI: ElectronAPI = {
isUsingGitHubFallback: (): Promise<boolean> => {
return ipcRenderer.invoke('is-using-github-fallback');
},
getAutoDownloadDisabled: (): Promise<boolean> => {
return ipcRenderer.invoke('get-auto-download-disabled');
},
closeWindow: () => ipcRenderer.send('close-window'),
hasAcceptedRecipeBefore: (recipe: Recipe) =>
ipcRenderer.invoke('has-accepted-recipe-before', recipe),
+47 -11
View File
@@ -46,6 +46,18 @@ let lastReportedProgress = 0;
// Track if IPC handlers have been registered
let ipcUpdateHandlersRegistered = false;
let autoDownloadDisabled = false;
export function setAutoDownloadDisabled(disabled: boolean) {
autoDownloadDisabled = disabled;
autoUpdater.autoDownload = !disabled;
log.info(`Auto-download ${disabled ? 'disabled' : 'enabled'}`);
}
export function getAutoDownloadDisabled(): boolean {
return autoDownloadDisabled;
}
// Register IPC handlers (only once)
export function registerUpdateIpcHandlers() {
if (ipcUpdateHandlersRegistered) {
@@ -154,9 +166,12 @@ export function registerUpdateIpcHandlers() {
updateTrayIcon(true);
sendStatusToWindow('update-available', { version: result.latestVersion });
// Auto-download for GitHub fallback (matching autoDownload behavior)
log.info('Auto-downloading update via GitHub fallback...');
await githubAutoDownload(result.downloadUrl!, result.latestVersion!, 'manual check');
if (!autoDownloadDisabled) {
log.info('Auto-downloading update via GitHub fallback...');
await githubAutoDownload(result.downloadUrl!, result.latestVersion!, 'manual check');
} else {
log.info('Auto-download disabled — skipping GitHub fallback download');
}
} else {
trackUpdateCheckCompleted('not_available', currentVersion, {
latestVersion: result.latestVersion,
@@ -333,6 +348,10 @@ export function registerUpdateIpcHandlers() {
ipcMain.handle('is-using-github-fallback', () => {
return isUsingGitHubFallback;
});
ipcMain.handle('get-auto-download-disabled', () => {
return autoDownloadDisabled;
});
}
// Configure auto-updater
@@ -368,8 +387,17 @@ export function setupAutoUpdater(tray?: Tray) {
log.error('Error getting feed URL:', e);
}
// Respect GOOSE_DISABLE_AUTO_DOWNLOAD env var (takes precedence over user setting)
const envDisabled =
process.env.GOOSE_DISABLE_AUTO_DOWNLOAD === '1' ||
process.env.GOOSE_DISABLE_AUTO_DOWNLOAD === 'true';
if (envDisabled) {
autoDownloadDisabled = true;
log.info('Auto-download disabled via GOOSE_DISABLE_AUTO_DOWNLOAD environment variable');
}
// Configure auto-updater settings
autoUpdater.autoDownload = true; // Automatically download updates when available
autoUpdater.autoDownload = !autoDownloadDisabled;
autoUpdater.autoInstallOnAppQuit = true;
// Enable updates in development mode for testing
@@ -485,9 +513,12 @@ export function setupAutoUpdater(tray?: Tray) {
updateTrayIcon(true);
sendStatusToWindow('update-available', { version: result.latestVersion });
// Auto-download for GitHub fallback (matching autoDownload behavior)
log.info('Auto-downloading update via GitHub fallback on startup...');
await githubAutoDownload(result.downloadUrl!, result.latestVersion!, 'on startup');
if (!autoDownloadDisabled) {
log.info('Auto-downloading update via GitHub fallback on startup...');
await githubAutoDownload(result.downloadUrl!, result.latestVersion!, 'on startup');
} else {
log.info('Auto-download disabled — skipping GitHub fallback download on startup');
}
} else {
trackUpdateCheckCompleted('not_available', currentVersion, {
latestVersion: result.latestVersion,
@@ -533,7 +564,9 @@ export function setupAutoUpdater(tray?: Tray) {
latestVersion: info.version,
usingFallback: false,
});
trackUpdateDownloadStarted(info.version, 'electron-updater');
if (!autoDownloadDisabled) {
trackUpdateDownloadStarted(info.version, 'electron-updater');
}
updateAvailable = true;
lastUpdateState = { updateAvailable: true, latestVersion: info.version };
updateTrayIcon(true);
@@ -591,9 +624,12 @@ export function setupAutoUpdater(tray?: Tray) {
updateTrayIcon(true);
sendStatusToWindow('update-available', { version: result.latestVersion });
// Auto-download for GitHub fallback (matching autoDownload behavior)
log.info('Auto-downloading update via GitHub fallback after error...');
await githubAutoDownload(result.downloadUrl!, result.latestVersion!, 'after error');
if (!autoDownloadDisabled) {
log.info('Auto-downloading update via GitHub fallback after error...');
await githubAutoDownload(result.downloadUrl!, result.latestVersion!, 'after error');
} else {
log.info('Auto-download disabled — skipping GitHub fallback download after error');
}
} else {
updateAvailable = false;
updateTrayIcon(false);
+2
View File
@@ -33,6 +33,7 @@ export type LanguageSetting = 'system' | 'en' | 'es' | 'hi' | 'ja' | 'ko' | 'ru'
export interface Settings {
// Desktop app settings
showMenuBarIcon: boolean;
disableAutoDownload: boolean;
showDockIcon: boolean;
enableWakelock: boolean;
enableNotifications: boolean;
@@ -70,6 +71,7 @@ export const defaultKeyboardShortcuts: DefaultKeyboardShortcuts = {
export const defaultSettings: Settings = {
// Desktop app settings
showMenuBarIcon: true,
disableAutoDownload: false,
showDockIcon: true,
enableWakelock: false,
enableNotifications: true,