Fix recurring update issues (related to #671)

This commit addresses three recurring issues with the update system:

1. **Checksum mismatches (v4.27.0, v4.28.0):**
   - Root cause: Release process uploads checksums.txt first, but if artifacts
     are rebuilt after that upload, checksums become stale
   - Fix: Update RELEASE_CHECKLIST.md to REQUIRE running validate-release.sh
     before publishing (step 9, non-negotiable)
   - The validation script exists and catches these errors, but wasn't being
     enforced in the release process

2. **Duplicate error modals:**
   - Root cause: UpdateProgressModal rendered in both App.tsx
     (GlobalUpdateProgressWatcher) and UpdateBanner.tsx
   - Fix: Remove UpdateProgressModal from UpdateBanner.tsx
   - GlobalUpdateProgressWatcher automatically shows the modal when updates
     start, so the banner's modal is redundant

3. **Rate limiting too strict:**
   - Root cause: UpdateProgressModal polls /api/updates/status every 2 seconds
     (30 req/min), but rate limit was 20/min
   - Fix: Increase UpdateEndpoints rate limit from 20/min to 60/min
   - Allows modal to poll without hitting rate limits during updates

These were all manual process errors and configuration issues, not code bugs.
The validation script enforcement prevents future checksum mismatches.
This commit is contained in:
rcourtman 2025-11-11 09:09:30 +00:00
parent 4c4fd3a99b
commit e894bc7b1d
2 changed files with 4 additions and 23 deletions

View file

@ -1,20 +1,14 @@
import { Show, createSignal, createEffect, For, useContext } from 'solid-js';
import { useNavigate } from '@solidjs/router';
import { Show, createSignal, createEffect, For } from 'solid-js';
import { updateStore } from '@/stores/updates';
import { UpdatesAPI, type UpdatePlan } from '@/api/updates';
import { UpdateConfirmationModal } from './UpdateConfirmationModal';
import { UpdateProgressModal } from './UpdateProgressModal';
import { WebSocketContext } from '@/App';
import { copyToClipboard } from '@/utils/clipboard';
import { logger } from '@/utils/logger';
export function UpdateBanner() {
const navigate = useNavigate();
const wsContext = useContext(WebSocketContext);
const [isExpanded, setIsExpanded] = createSignal(false);
const [updatePlan, setUpdatePlan] = createSignal<UpdatePlan | null>(null);
const [showConfirmModal, setShowConfirmModal] = createSignal(false);
const [showProgressModal, setShowProgressModal] = createSignal(false);
const [isApplying, setIsApplying] = createSignal(false);
const [copiedIndex, setCopiedIndex] = createSignal<number | null>(null);
@ -42,9 +36,8 @@ export function UpdateBanner() {
setIsApplying(true);
try {
await UpdatesAPI.applyUpdate(info.downloadUrl);
// Close confirmation and show progress
// Close confirmation - GlobalUpdateProgressWatcher will auto-open the progress modal
setShowConfirmModal(false);
setShowProgressModal(true);
} catch (error) {
logger.error('Failed to start update', error);
alert('Failed to start update. Please try again.');
@ -295,18 +288,6 @@ export function UpdateBanner() {
}}
isApplying={isApplying()}
/>
{/* Update Progress Modal */}
<UpdateProgressModal
isOpen={showProgressModal()}
onClose={() => setShowProgressModal(false)}
onViewHistory={() => {
setShowProgressModal(false);
navigate('/settings/updates');
}}
connected={wsContext?.connected}
reconnecting={wsContext?.reconnecting}
/>
</Show>
);
}

View file

@ -36,8 +36,8 @@ func InitializeRateLimiters() {
// Recovery operations: extremely strict
RecoveryEndpoints: NewRateLimiter(3, 10*time.Minute), // 3 attempts per 10 minutes
// Update operations: moderate limits
UpdateEndpoints: NewRateLimiter(20, 1*time.Minute), // 20 checks per minute
// Update operations: allow frequent polling during updates
UpdateEndpoints: NewRateLimiter(60, 1*time.Minute), // 60 checks per minute (modal polls every 2s)
// WebSocket connections: per-connection limits
WebSocketEndpoints: NewRateLimiter(30, 1*time.Minute), // 30 new connections per minute