diff --git a/install.ps1 b/install.ps1 index fd56fa47..3799ebb3 100644 --- a/install.ps1 +++ b/install.ps1 @@ -46,7 +46,7 @@ try { } $actual = (Get-FileHash -LiteralPath $temporary -Algorithm SHA256).Hash.ToLowerInvariant() if ($actual -ne $expected.ToLowerInvariant()) { - throw "The downloaded installer does not match its expected fingerprint. Nothing was run and nothing was written. The download was most likely cut short; try again, on another network if possible." + throw "The downloaded installer does not match its expected fingerprint. Nothing was run and nothing was written. The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages; waiting five minutes settles that." } $installer = $temporary } diff --git a/install.sh b/install.sh index c3c595c7..b90fe852 100755 --- a/install.sh +++ b/install.sh @@ -91,7 +91,9 @@ if [ -z "$INSTALLER" ] || [ ! -f "$INSTALLER" ]; then if [ "$actual" != "$expected" ]; then echo "Error: the downloaded installer does not match its expected fingerprint." >&2 echo " Nothing was run and nothing was written." >&2 - echo " The download was most likely cut short. Try again, on another network if possible." >&2 + echo " The download was most likely cut short: try again. Within minutes of an update" >&2 + echo " of this project the two files can come from caches of different ages; waiting" >&2 + echo " five minutes settles that." >&2 exit 1 fi INSTALLER="$TEMP_INSTALLER" diff --git a/tests/test_install.py b/tests/test_install.py index 9541af2e..000b18c8 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -2657,5 +2657,20 @@ class TheProgressLineFitsTheTerminal(unittest.TestCase): self.assertIn("%", line) + +class ThePagesQuoteTheBootstrapsOwnWords(unittest.TestCase): + """The installer page and the FAQ quoted "install.py SHA-256 mismatch", + a message the bootstraps no longer print.""" + + def test_the_quoted_message_is_printed(self): + quoted = "does not match its expected" + for wrapper in ("install.sh", "install.ps1"): + self.assertIn(quoted, (REPO_ROOT / wrapper).read_text(encoding="utf-8")) + for page in ("wiki/installer.md", "wiki/faq.md"): + text = (REPO_ROOT / page).read_text(encoding="utf-8") + self.assertNotIn("SHA-256 mismatch`", text) + self.assertIn(quoted, " ".join(text.split())) + + if __name__ == "__main__": unittest.main() diff --git a/wiki/faq.md b/wiki/faq.md index 028edaa4..a0308877 100644 --- a/wiki/faq.md +++ b/wiki/faq.md @@ -103,8 +103,8 @@ The `--verbose` flag shows source references and expected values from the emulat The one-liner runs a small bootstrap, not the installer. That bootstrap fetches `install.py` over HTTPS only, refuses anything over 2 MB, and requires its SHA-256 to equal the value written inside the bootstrap itself. A substituted -installer aborts with `install.py SHA-256 mismatch` before a single line of it -runs. +installer aborts with "the downloaded installer does not match its expected +fingerprint" before a single line of it runs. The installer then verifies every file it downloads against the size, SHA-256 and SHA-1 the manifest declares, and moves it into place only once those match. diff --git a/wiki/installer.md b/wiki/installer.md index fe4050ec..844f2041 100644 --- a/wiki/installer.md +++ b/wiki/installer.md @@ -52,8 +52,11 @@ pinned raw URL. A URL with any other scheme is refused outright. They cap the download at 2 MB and require the SHA-256 to equal the 64 hex characters embedded in the wrapper, `RETROBIOS_INSTALL_SHA256` when set. A -mismatch aborts with `install.py SHA-256 mismatch` and nothing runs. Any change -to `install.py` therefore means recomputing that pin in both wrappers. +mismatch aborts with "the downloaded installer does not match its expected +fingerprint" and nothing runs. Right after an update of this project the +wrapper and `install.py` can come from caches a few minutes apart, which reads +the same way: waiting five minutes settles it. Any change to `install.py` +therefore means recomputing that pin in both wrappers. They locate a Python 3.8 or newer interpreter (`python3` then `python` on POSIX, `py -3` then `python3` then `python` on Windows) and refuse to continue