Repository navigation
Let admins update RetroDB with one click from GitHub Releases - #23
Merged
Merged
Conversation
- Screenshot dedup scans/deletes under IMAGE_PATH, not the bundle STATIC_PATH - Multi-disc scan lists each disc file once (case-insensitive FS double-count) - Default-controller auto-assign tries longest folder pattern first (snes != nes) - /api/controllers: one systems query instead of one per controller - Calculate storage: one batched write instead of a commit per game - Local search clamps negative limit; similar-games escapes LIKE wildcards - AI Fill invalidates the Library filter cache - Case-only ROM renames allowed (samefile check) - Database optimize closes its connection when VACUUM fails - ROM-tools task cleanup iterates a snapshot; browse-folders single scandir Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013s5ztLFVCcdTjjTeVcTnJr
services: - Clear scraped data commits the filename-title reset (close() rolled it back) - Analytics "not started" sums NULL and 'not_started' groups - query/execute helpers close connections they open outside an app context - JPEG save converts P/LA/PA/I/F modes, not only RGBA - Orphan-media scan looks basenames up in a set (was orphans x references) jobs: - Per-system RA refresh accepted system_name (TypeError -> 500 before) - Swapped/demoted bulk scrapes drop their 'queued' job_queue row - Bulk scrape settings load inside the try (no stuck 'running' job) - HLTB / alt-titles jobs commit per game, not holding the writer lock across 25 network calls - Player-core worker re-runs a request that raced its shutdown - Museum image fetch: import/connect failures end the job cleanly - Recovery dedup no longer collapses distinct multi-system scrapes - GUI installer surfaces worker errors; .desktop Exec paths are quoted Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013s5ztLFVCcdTjjTeVcTnJr
- Card grid: one-column phone layout no longer gets inline row lines; re-layout when a resize changes the column count; measure all cards before placing (one forced layout instead of one per card) - Game detail modal: late replies no longer paint a closed/other game; error message no longer replaces the spinner for good; AI Fill only reopens the game still shown - Job toasts: a slow status reply is no longer aborted by the next poll - Stale-reply guards: museum status, version search, log viewer, official-names proposals; Kill closes the Now-playing popover - CHD converter/verify escape disk file names in innerHTML - Replace undefined CSS variables with existing tokens (skip-link focus outline, settings tab bar, rename modal backgrounds were invisible) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013s5ztLFVCcdTjjTeVcTnJr
- The last active admin can no longer be demoted or deactivated - /api/psn/games clamps page/per_page (per_page=0 was a 500) - Steam/Xbox/PSN library fetch: duplicate check uses one index built from one query instead of re-reading the system per fetched title - /trophies reads and cleans PlayStation titles once, not per trophy set; PSN full sync cleans each title once - PSN single-game sync writes trophies in one batch; detail-page debug logging moved from INFO to DEBUG - Collector trophies refresh: one read and one transaction (~140 round trips before); sidebar rank cached per request Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013s5ztLFVCcdTjjTeVcTnJr
- IGDB age ratings read IGDB's single global rating enum (CERO/USK/GRAC/ ClassInd/ACB were mapped as 1-5) and the newer organization / rating_category shape; hybrid fetch requests those fields - IGDB players: null offlinemax no longer aborts the merge; no made-up 1 - ScreenScraper primary reads the flat get_game_info dict on a cache miss - ScreenScraper media extensions come from the URL path, not the query string (which carried account credentials into file names) - TGDB lookup tables: a failed fetch is retried, not cached empty; unknown IDs are dropped instead of written as bare numbers - TGDB / RAWG / ES-DE no longer invent players=1 (ES-DE overwrote curated players/modes through COALESCE) - ES-DE gamelist: full-path match wins over a same-filename match - RetroAchievements: curly apostrophes normalised; search title once - Scans keep an existing system logo; scan_roms finds SVG logos Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013s5ztLFVCcdTjjTeVcTnJr
- all-games-controller: the resize handler called debounce() at load, before the deferred core bundle defines it, so the Library and System pages threw and rendered no cards. Use a local timer. - The column count read from computed grid-template-columns includes the implicit tracks the cards' own inline placement created, so a grid narrowed to one column (phone) kept laying out two columns with a huge gap. A full layout clears inline placement first; a ResizeObserver on the grid re-lays out once its width settles (a window-resize timer fired mid sidebar animation and read the wrong count). - screenscraper_password / screenscraper_devpassword join SECRET_API_KEY_FIELDS: masked on GET /api/scraper-settings and the settings page, kept on a masked echo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013s5ztLFVCcdTjjTeVcTnJr
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013s5ztLFVCcdTjjTeVcTnJr
- /api/jobs/dismiss checks the same role / ownership as resume (_may_manage_job, shared); any signed-in user could dismiss any job - The dashboard recovery banner leaves out jobs the user may neither resume nor dismiss (a player no longer sees an interrupted bulk scrape) - Demoting or deactivating the last admin gets its own message; catalogs regenerated - CLAUDE.md: a release needs at least 5 changelog items, no upper limit (user, 2026-10-27) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013s5ztLFVCcdTjjTeVcTnJr
Admins see a newer published release on the dashboard and in Settings > System > Updates with its notes. Download fetches the release asset for this install (source zip or standalone bundle), checks it against the release's SHA-256 file and unpacks it safely (no path escape, no links, executable bits kept). Install backs up the database, writes a plan and starts the NEW version's helper (updater_apply.py, or the standalone binary with --apply-update, dispatched before app.py imports anything), then shuts down. The helper waits for the old process, swaps the files (source: per file with a manifest of installed files; standalone: the whole _internal/), installs changed dependencies from requirements.lock, restarts and waits for /health to report the new version. Any failure puts files and database back and restarts the old version; the outcome is shown to the admin. User data is never written: database, logs, data/*.json settings and tokens, scraped media. config.py (app constants, APP_VERSION) follows the new config.example.py, the old one kept for rollback. Copied files get a fresh mtime and __pycache__ is cleared, after an end-to-end run showed a same-size config.py loading its stale .pyc. Git checkouts and LocalWebServerManager installs see the notice but are not updated. Daily check toggle: update_check_auto. Spec docs/specs/PASS-59-169-self-updater.md; roadmap 59.169, and 59.170 for cosign signature verification. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013s5ztLFVCcdTjjTeVcTnJr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
RetroDB checks GitHub Releases for a newer version. Admins see it on the dashboard and in Settings > System > Updates, with the release notes. Download fetches and verifies the release, and Install and restart applies it. If the new version does not come up, RetroDB rolls back to the old version by itself. Both source and standalone installs are covered.
This PR is built on #21's branch and targets
mainso that CI runs on it. Until #21 merges, this diff also shows #21's commits; merge #21 first.Why
This is PASS-59-169, requested 2026-10-27. Your decisions:
The spec is
docs/specs/PASS-59-169-self-updater.md.How
Checking.
services/updater.pyasksreleases/latestat most once a day, or when you click Check now. Draft releases are invisible to that endpoint, so publishing the draft is what offers the update to users.Downloading.
RetroDB-v{V}-{Platform}.zipwith its line inSHA256SUMS.txt, or…-Standalone.zipwith its.sha256.https://github.com/milnet01/RetroDB/releases/download/...and symlinks, and keeps Unix permission bits.Installing.
database/backups/pre_update_*.db, writes a plan, and starts the new version's helper. For source installs that isupdater_apply.py; for standalone,retrodb --apply-update, which the top ofapp.pyhands over before importing anything._internal/folder plus the program.requirements.lockchanged./healthto report the new version. Any failure restores the files and the database and restarts the old version. The outcome is written for the admin to see.Never written:
database/,logs/,data/settings and tokens, scraped media.config.pyholds only app constants, includingAPP_VERSION, so it is replaced from the newconfig.example.py. The old copy stays in the rollback folder.Not one-click: git checkouts and LocalWebServerManager-managed installs see the notice and a link, but are not updated.
Threat model (spec §7): the checksum catches corruption and a file swapped in transit. Checking the cosign signatures
release.ymlalready attaches is roadmap 59.170.Mandatory-workflow checklist
features/updates.css, added tobuild_css.py.updates.js, registered inbuild_js.py; the i18n manifest is regenerated.tests/test_updater.py..claude/rules/distribution.mdnotes that release asset names are now load-bearing.Verification
End to end, on a real source install of this branch. A copy of the tree with no
.gitran on port 5088. I ran the real helper exactly as Install does, using the installed version's ownservices/updater.pyfor the plan, backup and spawn:/healthreports 3.33.1,config.pyreads 3.33.1, the staging tree is cleaned up androllback-3.33.0is kept.config.py, the new file the release added is removed, the database is restored from the backup, and the manifest is unchanged.config.pyc. Python checks a.pycby the source's size and whole-second mtime, andcopy2carried over the staged mtime, so a same-sizeconfig.pyedit was not recompiled. Files are now copied with a fresh mtime and__pycache__is cleared. A regression test reproduces it.Browser walk (Chromium, 1400px and 375px):
/settings#updatesopens the card;The walk also caught
.btnoverriding[hidden]on the buttons; fixed.Not verified:
Roadmap 59.169 lists trying it against the first real published release after this ships.
🤖 Generated with Claude Code
https://claude.ai/code/session_013s5ztLFVCcdTjjTeVcTnJr