max / alloy
1 file changed,
+36 insertions,
-2 deletions
| @@ -20,7 +20,10 @@ | |||
| 20 | 20 | the screen exists to be checked, and a reader cannot check what it does not | |
| 21 | 21 | attribute. | |
| 22 | 22 | 4. **The install completes**, so a skipped step is a step that was genuinely | |
| 23 | - | answered rather than one that was merely hidden. | |
| 23 | + | answered rather than one that was merely hidden. fw12's recipe encrypts, so | |
| 24 | + | this is also the first scenario to drive the encrypted path to its end: it | |
| 25 | + | reads the recovery phrase off the screen and types it back, which is what | |
| 26 | + | the installer waits for before it will say "finished". | |
| 24 | 27 | ||
| 25 | 28 | ## Why the target is an NVMe | |
| 26 | 29 | ||
| @@ -50,6 +53,34 @@ | |||
| 50 | 53 | ||
| 51 | 54 | STEP = re.compile(r"step (\d+) of 6") | |
| 52 | 55 | ||
| 56 | + | # The recovery phrase, as `render_recovery` draws it: eight lowercase words on | |
| 57 | + | # their own indented line, in the action color, between the prose above and the | |
| 58 | + | # field below. Anchored on the shape rather than on a line number, because the | |
| 59 | + | # prose around it is the part likely to be reworded. | |
| 60 | + | PHRASE = re.compile(r"^\s{2,}([a-z]+(?: [a-z]+){7})\s*$", re.M) | |
| 61 | + | ||
| 62 | + | ||
| 63 | + | def finish_encrypted(s, timeout): | |
| 64 | + | """Answer the recovery gate, which only an encrypted install reaches. | |
| 65 | + | ||
| 66 | + | The install is over by the time this screen appears; what it gates is the | |
| 67 | + | installer saying "finished, reboot". A user who reboots without these words | |
| 68 | + | has a machine whose disk dies with its TPM, so the installer will not move | |
| 69 | + | on until they are typed back, and a scenario that stops here would leave the | |
| 70 | + | encrypted path asserted only as far as the last command. | |
| 71 | + | ||
| 72 | + | `install_drive.py` defaults encryption off and never reaches this, which is | |
| 73 | + | why this lives here rather than there.""" | |
| 74 | + | s.wait_for(r"Type it back to confirm you have it", timeout) | |
| 75 | + | s.pump(0.5) | |
| 76 | + | screen = s.screen.text() | |
| 77 | + | check("Installation finished" in screen, | |
| 78 | + | "the recovery screen says the install did not finish", s) | |
| 79 | + | hit = PHRASE.search(screen) | |
| 80 | + | check(hit is not None, "no eight-word recovery phrase on the screen that asks for one", s) | |
| 81 | + | s.send(hit.group(1)) | |
| 82 | + | s.send(ENTER) | |
| 83 | + | ||
| 53 | 84 | ||
| 54 | 85 | def current_step(s): | |
| 55 | 86 | """The step number the title is showing, or None if no title is up.""" | |
| @@ -134,7 +165,10 @@ | |||
| 134 | 165 | s.send(ENTER) | |
| 135 | 166 | ||
| 136 | 167 | print("==> installing; this is the long part", flush=True) | |
| 137 | - | ok = wait_for_verdict(s, args.install_timeout) | |
| 168 | + | # Encrypted installs stop on the recovery phrase before they will admit | |
| 169 | + | # to being finished. Answer it, then read the footer as usual. | |
| 170 | + | finish_encrypted(s, args.install_timeout) | |
| 171 | + | ok = wait_for_verdict(s, 120) | |
| 138 | 172 | print(s.screen.text()) | |
| 139 | 173 | if not ok: | |
| 140 | 174 | print("\nerror: the installer finished without offering a reboot, " |