ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
fleet_wsl_stage.py
Go to the documentation of this file.
1# SPDX-License-Identifier: MIT
2# Copyright (c) 2026 Brighton Sikarskie
3"""Own and publish the WSL fleet's staged control bytes without path races."""
4
5from __future__ import annotations
6
7import hashlib
8import json
9import os
10import pwd
11import shlex
12import shutil
13import stat
14import subprocess
15import sys
16import tempfile
17from collections.abc import Callable
18from pathlib import Path
19from typing import Any
20
21import fleet_model as fm
22import fleet_reach as fr
23import fleet_runner_maintenance as frm
24
25WSL_STAGE = "/opt/ra8-infra"
26SHA256_HEX_LENGTH = 64
27ROOT_READ_MODE = 0o444
28WSL_RUNNER_IMAGE_CACHE = "/opt/ra8-infra-cache/ra8-ci-runner.tar"
29STAGE_OWNER = "ra8-firmware fleet WSL stage v1"
30CACHE_OWNER = "ra8-firmware fleet WSL runner cache v1"
31OWNER_FILE = ".ra8-fleet-owner"
32GENERATION_FILE = ".ra8-stage-generation"
33REMOTE_APPLY_REQUIRED_STATUS = 42
34STAGE_DIRECTORY_MEMBERS = (
35 ".ansible/collections",
36 ".tools/uv",
37 "infra/ansible",
38)
39STAGE_MEMBERS = (
40 *STAGE_DIRECTORY_MEMBERS,
41 "pyproject.toml",
42 "scripts/checks/check_ansible_collections.py",
43 "scripts/ci/fleet_capacity.sh",
44 "scripts/dev/bootstrap_uv.py",
45 "scripts/dev/bootstrap_uv_exec.py",
46 "scripts/dev/fleet_runner_maintenance.py",
47 "scripts/dev/fleet_path_authority.py",
48 "scripts/dev/uv_release.json",
49 "scripts/dev/verify_locked_environment.py",
50 "uv.lock",
51)
52
53CommandRunner = Callable[..., int]
54
55
56def _fail(message: str) -> int:
57 """Print one transport failure and return the fleet precondition status."""
58 print(f"fleet: error: {message}", file=sys.stderr)
59 return 2
60
61
62def _bootstrap_environment() -> dict[str, str]:
63 """Return a local uv bootstrap environment without inherited controls."""
64 clean = {
65 "HOME": pwd.getpwuid(os.getuid()).pw_dir,
66 "LANG": "C.UTF-8",
67 "LC_ALL": "C.UTF-8",
68 "PATH": "/usr/bin:/bin",
69 }
70 clean["PYTHONNOUSERSITE"] = "1"
71 return clean
72
73
74def _links_stay_within(root: Path) -> bool:
75 """Accept only existing relative links whose targets stay below ``root``."""
76 authority = root.resolve(strict=True)
77 for entry in root.rglob("*"):
78 if not entry.is_symlink():
79 continue
80 try:
81 target = entry.resolve(strict=True)
82 except (OSError, RuntimeError):
83 return False
84 if entry.readlink().is_absolute() or not target.is_relative_to(authority):
85 return False
86 return True
87
88
89def _installed_snapshot() -> bool:
90 """Return whether this source is the root-owned immutable service snapshot."""
91 marker = fm.REPO_ROOT / ".ra8-source-sha256"
92 try:
93 root_metadata = fm.REPO_ROOT.lstat()
94 metadata = marker.lstat()
95 digest = marker.read_text(encoding="ascii").strip()
96 except OSError:
97 return False
98 return (
99 not fm.REPO_ROOT.is_symlink()
100 and root_metadata.st_uid == 0
101 and stat.S_ISREG(metadata.st_mode)
102 and not marker.is_symlink()
103 and metadata.st_uid == 0
104 and stat.S_IMODE(metadata.st_mode) == ROOT_READ_MODE
105 and len(digest) == SHA256_HEX_LENGTH
106 and all(character in "0123456789abcdef" for character in digest)
107 )
108
109
110def _bootstrap_action(mode: str, installed: bool) -> str:
111 """Select mutation only for an operator-owned apply checkout."""
112 return "--ensure" if mode == "apply" and not installed else "--verify-cache"
113
114
115def verify_stage_sources(mode: str) -> int:
116 """Authenticate local staged authorities before any remote side effect."""
117 try:
118 frm.ansible_environment(os.environ, fm.ANSIBLE_DIR)
119 except frm.MaintenanceError as exc:
120 return _fail(str(exc))
121 bootstrap = fm.REPO_ROOT / "scripts/dev/bootstrap_uv.py"
122 action = _bootstrap_action(mode, _installed_snapshot())
123 result = subprocess.run( # noqa: S603 -- fixed repository tool and managed Python
124 [sys.executable, str(bootstrap), action],
125 cwd=fm.REPO_ROOT,
126 env=_bootstrap_environment(),
127 text=True,
128 capture_output=True,
129 check=False,
130 timeout=120,
131 )
132 if result.returncode:
133 sys.stderr.write(result.stderr)
134 return _fail("pinned uv cache is unavailable for the WSL stage")
135 for relative in STAGE_MEMBERS:
136 path = fm.REPO_ROOT / relative
137 if not path.exists() or path.is_symlink():
138 return _fail(f"WSL stage authority is absent or linked: {relative}")
139 if path.is_dir() and not _links_stay_within(path):
140 return _fail(f"WSL stage authority contains an escaping link: {relative}")
141 return 0
142
143
144def _generation_records(root: Path) -> list[tuple[str, str, int, str]]:
145 """Describe every staged path by name, type, mode, and authenticated payload."""
146 records: list[tuple[str, str, int, str]] = []
147 for member in STAGE_MEMBERS:
148 start = root / member
149 paths = [start]
150 if start.is_dir():
151 paths.extend(sorted(start.rglob("*"), key=lambda path: path.as_posix()))
152 for path in paths:
153 metadata = path.lstat()
154 relative = path.relative_to(root).as_posix()
155 mode = stat.S_IMODE(metadata.st_mode)
156 if path.is_symlink():
157 records.append((relative, "l", mode, str(path.readlink())))
158 elif path.is_dir():
159 records.append((relative, "d", mode, ""))
160 elif path.is_file():
161 digest = hashlib.sha256(path.read_bytes()).hexdigest()
162 records.append((relative, "f", mode, digest))
163 else:
164 msg = f"unsupported WSL stage authority type: {relative}"
165 raise ValueError(msg)
166 return records
167
168
169def stage_generation(root: Path = fm.REPO_ROOT) -> str:
170 """Return the canonical complete-generation digest for staged authorities."""
171 encoded = json.dumps(_generation_records(root), separators=(",", ":")).encode("ascii")
172 return hashlib.sha256(encoded).hexdigest()
173
174
175def _generation_probe_command(root: str) -> str:
176 """Render a read-only complete-generation digest probe for the remote stage."""
177 code = """import hashlib,json,os,stat,sys
178from pathlib import Path
179root=Path(sys.argv[1])
180members=json.loads(sys.argv[2])
181records=[]
182for member in members:
183 start=root/member
184 paths=[start]
185 if start.is_dir():
186 paths.extend(sorted(start.rglob("*"),key=lambda path:path.as_posix()))
187 for path in paths:
188 metadata=path.lstat()
189 relative=path.relative_to(root).as_posix()
190 mode=stat.S_IMODE(metadata.st_mode)
191 if path.is_symlink():
192 records.append((relative,"l",mode,os.readlink(path)))
193 elif path.is_dir():
194 records.append((relative,"d",mode,""))
195 elif path.is_file():
196 records.append((relative,"f",mode,hashlib.sha256(path.read_bytes()).hexdigest()))
197 else:
198 raise SystemExit("unsupported staged path type: "+relative)
199print(hashlib.sha256(json.dumps(records,separators=(",",":")).encode("ascii")).hexdigest())
200"""
201 members = json.dumps(STAGE_MEMBERS)
202 return (
203 f"/usr/bin/python3 -I -S -c {shlex.quote(code)} {shlex.quote(root)} {shlex.quote(members)}"
204 )
205
206
207def _stage_archive(mode: str) -> tuple[int, bytes]:
208 """Build one authenticated local archive before touching the remote stage."""
209 rc = verify_stage_sources(mode)
210 if rc:
211 return rc, b""
212 tar_tool = Path("/usr/bin/tar")
213 if not tar_tool.is_file() or tar_tool.is_symlink() or not os.access(tar_tool, os.X_OK):
214 return _fail("trusted /usr/bin/tar is unavailable"), b""
215 tar = subprocess.run( # noqa: S603 -- fixed argv and checkout paths
216 [
217 str(tar_tool),
218 "--no-xattrs",
219 "-czf",
220 "-",
221 "-C",
222 str(fm.REPO_ROOT),
223 *STAGE_MEMBERS,
224 ],
225 capture_output=True,
226 check=False,
227 )
228 if tar.returncode:
229 sys.stderr.write(tar.stderr.decode("utf-8", "replace"))
230 return tar.returncode, tar.stdout
231
232
233def _owned_shell(owner: str, owner_uid: int = 0) -> list[str]:
234 """Render reusable exact-owner and no-mount directory operations."""
235 return [
236 f"expected_owner={shlex.quote(owner)}",
237 f"expected_owner_uid={owner_uid}",
238 "owned_dir() {",
239 ' [ -d "$1" ] && [ ! -L "$1" ] && ! /usr/bin/mountpoint -q -- "$1" &&',
240 ' [ "$(stat -c %u -- "$1")" = "$expected_owner_uid" ] &&',
241 ' [ "$(stat -c %a -- "$1")" = 755 ] &&',
242 f' [ -f "$1/{OWNER_FILE}" ] && [ ! -L "$1/{OWNER_FILE}" ] &&',
243 f' [ "$(stat -c %u -- "$1/{OWNER_FILE}")" = "$expected_owner_uid" ] &&',
244 f' [ "$(stat -c %a -- "$1/{OWNER_FILE}")" = 644 ] &&',
245 f' [ "$(cat -- "$1/{OWNER_FILE}")" = "$expected_owner" ]',
246 "}",
247 "sync_file() {",
248 " /usr/bin/python3 -I -S -c 'import os,sys; "
249 "f=os.open(sys.argv[1],os.O_RDONLY|os.O_NOFOLLOW); "
250 'os.fsync(f); os.close(f)\' "$1"',
251 "}",
252 "sync_dir() {",
253 " /usr/bin/python3 -I -S -c 'import os,sys; "
254 "f=os.open(sys.argv[1],os.O_RDONLY|os.O_DIRECTORY); "
255 'os.fsync(f); os.close(f)\' "$1"',
256 "}",
257 "remove_owned_dir() {",
258 ' owned_dir "$1" || { echo "refusing unowned WSL path: $1" >&2; exit 1; }',
259 ' parent="$(dirname -- "$1")"',
260 ' rm -rf --one-file-system -- "$1"',
261 ' sync_dir "$parent"',
262 "}",
263 ]
264
265
266def transaction_lock_lines(
267 exclusive: bool, lock_root: str = "/run/lock", owner_uid: int = 0
268) -> list[str]:
269 """Render a no-write host-local reader or writer lock acquisition."""
270 option = "-x" if exclusive else "-s"
271 return [
272 f"lock_root={shlex.quote(lock_root)}",
273 '[ -d "$lock_root" ] && [ ! -L "$lock_root" ] &&',
274 ' [ "$(readlink -f -- "$lock_root")" = "$lock_root" ] &&',
275 f' [ "$(stat -c %u -- "$lock_root")" = {owner_uid} ] || {{',
276 ' echo "unsafe WSL lock authority" >&2; exit 1;',
277 "}",
278 'case "$(stat -c %a -- "$lock_root")" in 755|775|1777) ;;',
279 ' *) echo "unsafe WSL lock authority mode" >&2; exit 1 ;;',
280 "esac",
281 'exec 9<"$lock_root"',
282 f"/usr/bin/flock {option} 9",
283 ]
284
285
286def stage_probe_lines(expected: str, stage: str = WSL_STAGE) -> list[str]:
287 """Render authenticated read-only classification of one installed generation."""
288 marker = f"{stage}/{GENERATION_FILE}"
289 probe = _generation_probe_command(stage)
290 return [
291 f"stage={shlex.quote(stage)}",
292 f"generation_marker={shlex.quote(marker)}",
293 'if [ ! -e "$stage" ] && [ ! -L "$stage" ]; then',
294 f' echo "WSL stage is missing; apply required" >&2; exit {REMOTE_APPLY_REQUIRED_STATUS}',
295 "fi",
296 *_owned_shell(STAGE_OWNER, 0 if stage == WSL_STAGE else os.getuid()),
297 'owned_dir "$stage" || { echo "unsafe WSL stage authority" >&2; exit 1; }',
298 'if [ ! -e "$generation_marker" ] && [ ! -L "$generation_marker" ]; then',
299 f' echo "WSL generation manifest is missing; apply required" >&2; '
300 f"exit {REMOTE_APPLY_REQUIRED_STATUS}",
301 "fi",
302 '[ -f "$generation_marker" ] && [ ! -L "$generation_marker" ] &&',
303 ' [ "$(stat -c %u -- "$generation_marker")" = "$expected_owner_uid" ] &&',
304 ' [ "$(stat -c %a -- "$generation_marker")" = 444 ] || {',
305 ' echo "unsafe WSL generation manifest" >&2; exit 1;',
306 "}",
307 'installed_generation="$(cat -- "$generation_marker")"',
308 f'if [ "$installed_generation" != {shlex.quote(expected)} ]; then',
309 f' echo "WSL stage is stale; apply required" >&2; exit {REMOTE_APPLY_REQUIRED_STATUS}',
310 "fi",
311 f'actual_generation="$({probe})" || {{',
312 ' echo "could not authenticate WSL stage generation" >&2; exit 1;',
313 "}",
314 '[ "$actual_generation" = "$installed_generation" ] || {',
315 ' echo "WSL stage generation authentication failed" >&2; exit 1;',
316 "}",
317 ]
318
319
320def stage_prepare_script(stage: str = WSL_STAGE) -> str:
321 """Render deterministic recovery and fresh incoming-stage creation."""
322 incoming = f"{stage}.incoming"
323 previous = f"{stage}.previous"
324 owner_uid = 0 if stage == WSL_STAGE else os.getuid()
325 lines = ["set -euo pipefail", *_owned_shell(STAGE_OWNER, owner_uid)]
326 lines.extend(
327 [
328 f"stage={shlex.quote(stage)}",
329 f"incoming={shlex.quote(incoming)}",
330 f"previous={shlex.quote(previous)}",
331 'if [ -e "$previous" ] || [ -L "$previous" ]; then',
332 ' owned_dir "$previous" || { echo "unowned previous WSL stage" >&2; exit 1; }',
333 ' if [ -e "$stage" ] || [ -L "$stage" ]; then',
334 ' owned_dir "$stage" || { echo "unowned current WSL stage" >&2; exit 1; }',
335 ' remove_owned_dir "$previous"',
336 " else",
337 ' mv -- "$previous" "$stage"',
338 ' sync_dir "$(dirname -- "$stage")"',
339 " fi",
340 "fi",
341 'if [ -e "$stage" ] || [ -L "$stage" ]; then',
342 ' owned_dir "$stage" || { echo "refusing unowned WSL stage" >&2; exit 1; }',
343 "fi",
344 'if [ -e "$incoming" ] || [ -L "$incoming" ]; then',
345 ' remove_owned_dir "$incoming"',
346 "fi",
347 'install -d -m 0755 -- "$incoming"',
348 f'printf \'%s\\n\' "$expected_owner" >"$incoming/{OWNER_FILE}"',
349 f'chmod 0644 "$incoming/{OWNER_FILE}"',
350 f'sync_file "$incoming/{OWNER_FILE}"',
351 'sync_dir "$incoming"',
352 'sync_dir "$(dirname -- "$incoming")"',
353 ]
354 )
355 return "\n".join(lines) + "\n"
356
357
358def stage_seal_script(generation: str, stage: str = WSL_STAGE) -> str:
359 """Authenticate an incoming generation and durably seal its manifest."""
360 incoming = f"{stage}.incoming"
361 marker = f"{incoming}/{GENERATION_FILE}"
362 probe = _generation_probe_command(incoming)
363 return "\n".join(
364 [
365 "set -euo pipefail",
366 *_owned_shell(STAGE_OWNER, 0 if stage == WSL_STAGE else os.getuid()),
367 f"incoming={shlex.quote(incoming)}",
368 'owned_dir "$incoming" || { echo "incoming WSL stage is not owned" >&2; exit 1; }',
369 f'actual_generation="$({probe})"',
370 f'[ "$actual_generation" = {shlex.quote(generation)} ] || {{',
371 ' echo "incoming WSL stage generation mismatch" >&2; exit 1;',
372 "}",
373 f"printf '%s\\n' {shlex.quote(generation)} >{shlex.quote(marker)}",
374 f"chmod 0444 {shlex.quote(marker)}",
375 f"sync_file {shlex.quote(marker)}",
376 'sync_dir "$incoming"',
377 "",
378 ]
379 )
380
381
382def stage_publish_script(stage: str = WSL_STAGE, generation: str | None = None) -> str:
383 """Render atomic stage publication with deterministic rollback."""
384 incoming = f"{stage}.incoming"
385 previous = f"{stage}.previous"
386 generation = generation or ""
387 owner_uid = 0 if stage == WSL_STAGE else os.getuid()
388 lines = ["set -euo pipefail", *_owned_shell(STAGE_OWNER, owner_uid)]
389 lines.extend(
390 [
391 f"stage={shlex.quote(stage)}",
392 f"incoming={shlex.quote(incoming)}",
393 f"previous={shlex.quote(previous)}",
394 'owned_dir "$incoming" || { echo "incoming WSL stage is not owned" >&2; exit 1; }',
395 f"generation_marker={shlex.quote(incoming + '/' + GENERATION_FILE)}",
396 '[ -f "$generation_marker" ] && [ ! -L "$generation_marker" ] &&',
397 ' [ "$(stat -c %u -- "$generation_marker")" = "$expected_owner_uid" ] &&',
398 ' [ "$(stat -c %a -- "$generation_marker")" = 444 ] || {',
399 ' echo "incoming WSL generation manifest is unsafe" >&2; exit 1;',
400 "}",
401 *(
402 [
403 f'[ "$(cat -- "$generation_marker")" = {shlex.quote(generation)} ] || {{',
404 ' echo "incoming WSL generation manifest is stale" >&2; exit 1;',
405 "}",
406 ]
407 if generation
408 else []
409 ),
410 '[ ! -e "$previous" ] && [ ! -L "$previous" ] || {',
411 ' echo "previous WSL stage was not recovered" >&2; exit 1;',
412 "}",
413 'if [ -e "$stage" ] || [ -L "$stage" ]; then',
414 ' owned_dir "$stage" || { echo "refusing unowned WSL stage" >&2; exit 1; }',
415 ' mv -- "$stage" "$previous"',
416 ' sync_dir "$(dirname -- "$stage")"',
417 "fi",
418 'if ! mv -- "$incoming" "$stage"; then',
419 ' [ ! -e "$previous" ] || mv -- "$previous" "$stage"',
420 ' sync_dir "$(dirname -- "$stage")"',
421 " exit 1",
422 "fi",
423 'sync_dir "$(dirname -- "$stage")"',
424 'if [ -e "$previous" ]; then remove_owned_dir "$previous"; fi',
425 ]
426 )
427 return "\n".join(lines) + "\n"
428
429
430def stage_cleanup_script(stage: str = WSL_STAGE) -> str:
431 """Render cleanup limited to the exact owned incoming directory."""
432 incoming = f"{stage}.incoming"
433 return "\n".join(
434 [
435 "set -euo pipefail",
436 *_owned_shell(STAGE_OWNER, 0 if stage == WSL_STAGE else os.getuid()),
437 f"incoming={shlex.quote(incoming)}",
438 'if [ -e "$incoming" ] || [ -L "$incoming" ]; then',
439 ' remove_owned_dir "$incoming"',
440 "fi",
441 "",
442 ]
443 )
444
445
446def prepare(data: dict[str, Any], name: str, mode: str, run: CommandRunner) -> tuple[int, str]:
447 """Transfer and authenticate an incoming generation without publishing it."""
448 tar_rc, archive = _stage_archive(mode)
449 if tar_rc:
450 return tar_rc, ""
451 generation = stage_generation()
452 host = data["hosts"][name]
453 ssh = fr.ssh_target(data, name)
454 shell = fm.remote_shell(host)
455 rc = run([*ssh, shell], stdin=stage_prepare_script())
456 if rc:
457 return rc, ""
458 distro = str(host["connect"]["distro"])
459 incoming = f"{WSL_STAGE}.incoming"
460 unpack = (
461 f"wsl -d {shlex.quote(distro)} -u root -e /usr/bin/env -i "
462 f"HOME=/root PATH=/usr/bin:/bin /usr/bin/tar -xzf - -C {shlex.quote(incoming)}"
463 )
464 rc = run([*ssh, unpack], stdin=archive)
465 if not rc:
466 rc = run([*ssh, shell], stdin=stage_seal_script(generation))
467 if rc:
468 run([*ssh, shell], stdin=stage_cleanup_script())
469 return rc, ""
470 return 0, generation
471
472
473def push(data: dict[str, Any], name: str, mode: str, run: CommandRunner) -> int:
474 """Atomically publish authenticated control inputs to the WSL distro."""
475 rc, generation = prepare(data, name, mode, run)
476 if rc:
477 return rc
478 host = data["hosts"][name]
479 ssh = fr.ssh_target(data, name)
480 shell = fm.remote_shell(host)
481 script = "\n".join(
482 [
483 "set -euo pipefail",
484 *transaction_lock_lines(exclusive=True),
485 stage_publish_script(generation=generation),
486 ]
487 )
488 rc = run([*ssh, shell], stdin=script)
489 if rc:
490 run([*ssh, shell], stdin=stage_cleanup_script())
491 return rc
492
493
494def cache_prepare_script(cache: str = WSL_RUNNER_IMAGE_CACHE) -> str:
495 """Render exact cache ownership and no-follow staging preparation."""
496 root = str(Path(cache).parent)
497 part = f"{cache}.part"
498 owner_uid = 0 if cache == WSL_RUNNER_IMAGE_CACHE else os.getuid()
499 lines = ["set -euo pipefail", *_owned_shell(CACHE_OWNER, owner_uid)]
500 lines.extend(
501 [
502 f"cache_root={shlex.quote(root)}",
503 f"dest={shlex.quote(cache)}",
504 f"part={shlex.quote(part)}",
505 'if [ -e "$cache_root" ] || [ -L "$cache_root" ]; then',
506 ' owned_dir "$cache_root" || { echo "refusing unowned runner cache" >&2; exit 1; }',
507 "else",
508 ' install -d -m 0755 -- "$cache_root"',
509 f' printf \'%s\\n\' "$expected_owner" >"$cache_root/{OWNER_FILE}"',
510 f' chmod 0644 "$cache_root/{OWNER_FILE}"',
511 f' sync_file "$cache_root/{OWNER_FILE}"',
512 ' sync_dir "$cache_root"',
513 ' sync_dir "$(dirname -- "$cache_root")"',
514 "fi",
515 'for path in "$dest" "$part"; do',
516 ' if [ -e "$path" ] || [ -L "$path" ]; then',
517 ' [ -f "$path" ] && [ ! -L "$path" ] && ! /usr/bin/mountpoint -q -- "$path" || {',
518 ' echo "refusing linked or non-file runner cache path: $path" >&2; exit 1;',
519 " }",
520 " fi",
521 "done",
522 'if [ -e "$part" ]; then',
523 ' rm -f -- "$part"',
524 ' sync_dir "$cache_root"',
525 "fi",
526 ]
527 )
528 return "\n".join(lines) + "\n"
529
530
531def cache_receive_command(distro: str, cache: str = WSL_RUNNER_IMAGE_CACHE) -> str:
532 """Return a Windows-shell-safe exclusive runner-image receiver."""
533 part = f"{cache}.part"
534 tokens = [
535 "wsl",
536 "-d",
537 distro,
538 "-u",
539 "root",
540 "-e",
541 "/usr/bin/env",
542 "-i",
543 "HOME=/root",
544 "PATH=/usr/bin:/bin",
545 "/usr/bin/dd",
546 f"of={part}",
547 "bs=4M",
548 "conv=fsync,excl",
549 "status=none",
550 ]
551 safe = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/._,:=+-"
552 if any(not token or any(character not in safe for character in token) for token in tokens):
553 message = "runner-image receiver cannot be represented safely for Windows"
554 raise ValueError(message)
555 return " ".join(tokens)
556
557
558def cache_cleanup_script(cache: str = WSL_RUNNER_IMAGE_CACHE) -> str:
559 """Remove only a regular part file below an exact owned cache root."""
560 root = str(Path(cache).parent)
561 part = f"{cache}.part"
562 return "\n".join(
563 [
564 "set -euo pipefail",
565 *_owned_shell(CACHE_OWNER, 0 if cache == WSL_RUNNER_IMAGE_CACHE else os.getuid()),
566 f"cache_root={shlex.quote(root)}",
567 f"part={shlex.quote(part)}",
568 'owned_dir "$cache_root" || { echo "runner cache ownership lost" >&2; exit 1; }',
569 'if [ -e "$part" ] || [ -L "$part" ]; then',
570 ' [ -f "$part" ] && [ ! -L "$part" ] || { exit 1; }',
571 ' rm -f -- "$part"',
572 ' sync_dir "$cache_root"',
573 "fi",
574 "",
575 ]
576 )
577
578
579def cache_publish_script(source_sha: str, cache: str = WSL_RUNNER_IMAGE_CACHE) -> str:
580 """Authenticate and atomically publish an owned runner-image part."""
581 root = str(Path(cache).parent)
582 part = f"{cache}.part"
583 owner_uid = 0 if cache == WSL_RUNNER_IMAGE_CACHE else os.getuid()
584 lines = ["set -euo pipefail", *_owned_shell(CACHE_OWNER, owner_uid)]
585 lines.extend(
586 [
587 f"cache_root={shlex.quote(root)}",
588 f"dest={shlex.quote(cache)}",
589 f"part={shlex.quote(part)}",
590 'owned_dir "$cache_root" || { echo "runner cache ownership lost" >&2; exit 1; }',
591 '[ -f "$part" ] && [ ! -L "$part" ] || { echo "runner cache part lost" >&2; exit 1; }',
592 'if [ -e "$dest" ] || [ -L "$dest" ]; then',
593 ' [ -f "$dest" ] && [ ! -L "$dest" ] || {',
594 ' echo "runner cache dest unsafe" >&2; exit 1;',
595 " }",
596 "fi",
597 'actual=$(sha256sum -- "$part")',
598 "actual=${actual%% *}",
599 f'if [ "$actual" != {shlex.quote(source_sha)} ]; then',
600 ' echo "runner image checksum mismatch" >&2',
601 " exit 1",
602 "fi",
603 'chmod 0644 "$part"',
604 'sync_file "$part"',
605 'mv -f -- "$part" "$dest"',
606 'sync_dir "$cache_root"',
607 ]
608 )
609 return "\n".join(lines) + "\n"
610
611
612def _run_shell(script: str) -> subprocess.CompletedProcess[str]:
613 """Run one offline transaction selftest shell."""
614 return subprocess.run(["/bin/bash"], input=script, text=True, capture_output=True, check=False)
615
616
617def _write_owner(path: Path, owner: str) -> None:
618 """Create one fixture-owned directory and exact marker."""
619 path.mkdir(parents=True)
620 (path / OWNER_FILE).write_text(f"{owner}\n", encoding="ascii")
621
622
623def _stage_selftest(root: Path) -> list[str]:
624 """Prove unowned preservation, transfer cleanup, and atomic replacement."""
625 failures: list[str] = []
626 stage = root / "stage"
627 stage.mkdir()
628 sentinel = stage / "preserve"
629 sentinel.write_text("unowned\n", encoding="ascii")
630 if _run_shell(stage_prepare_script(str(stage))).returncode == 0 or not sentinel.exists():
631 failures.append("unowned WSL stage was replaced")
632 shutil.rmtree(stage)
633 _write_owner(stage, STAGE_OWNER)
634 sentinel = stage / "last-good"
635 sentinel.write_text("keep\n", encoding="ascii")
636 if _run_shell(stage_prepare_script(str(stage))).returncode:
637 failures.append("owned WSL stage preparation failed")
638 return failures
639 incoming = Path(f"{stage}.incoming")
640 (incoming / "partial").write_text("partial\n", encoding="ascii")
641 if _run_shell(stage_cleanup_script(str(stage))).returncode or not sentinel.exists():
642 failures.append("failed transfer did not preserve the last-good WSL stage")
643 if _run_shell(stage_prepare_script(str(stage))).returncode:
644 failures.append("second owned WSL stage preparation failed")
645 return failures
646 incoming = Path(f"{stage}.incoming")
647 (incoming / "new").write_text("new\n", encoding="ascii")
648 generation = "a" * SHA256_HEX_LENGTH
649 marker = incoming / GENERATION_FILE
650 marker.write_text(f"{generation}\n", encoding="ascii")
651 marker.chmod(ROOT_READ_MODE)
652 if (
653 _run_shell(stage_publish_script(str(stage), generation)).returncode
654 or not (stage / "new").is_file()
655 ):
656 failures.append("owned WSL stage did not publish atomically")
657 previous = Path(f"{stage}.previous")
658 stage.rename(previous)
659 if _run_shell(stage_prepare_script(str(stage))).returncode or not (stage / "new").is_file():
660 failures.append("interrupted WSL publication did not recover the last-good generation")
661 _run_shell(stage_cleanup_script(str(stage)))
662 return failures
663
664
665def _cache_selftest(root: Path) -> list[str]:
666 """Prove unowned cache and planted-part links are preserved/refused."""
667 failures: list[str] = []
668 cache_root = root / "cache"
669 cache = cache_root / "runner.tar"
670 cache_root.mkdir()
671 sentinel = cache_root / "preserve"
672 sentinel.write_text("unowned\n", encoding="ascii")
673 if _run_shell(cache_prepare_script(str(cache))).returncode == 0 or not sentinel.exists():
674 failures.append("unowned runner cache was claimed or removed")
675 shutil.rmtree(cache_root)
676 _write_owner(cache_root, CACHE_OWNER)
677 outside = root / "outside"
678 outside.write_text("keep\n", encoding="ascii")
679 Path(f"{cache}.part").symlink_to(outside)
680 if _run_shell(cache_prepare_script(str(cache))).returncode == 0:
681 failures.append("planted runner-cache part symlink was accepted")
682 if outside.read_text(encoding="ascii") != "keep\n":
683 failures.append("planted runner-cache part symlink target was changed")
684 Path(f"{cache}.part").unlink()
685 cache.symlink_to(outside)
686 if _run_shell(cache_prepare_script(str(cache))).returncode == 0:
687 failures.append("planted runner-cache destination symlink was accepted")
688 if outside.read_text(encoding="ascii") != "keep\n":
689 failures.append("planted runner-cache destination target was changed")
690 return failures
691
692
693def _link_selftest(root: Path) -> list[str]:
694 """Prove installed internal links pass while external links fail closed."""
695 failures: list[str] = []
696 authority = root / "authority"
697 authority.mkdir()
698 (authority / "target").write_text("owned\n", encoding="ascii")
699 (authority / "internal").symlink_to("target")
700 if not _links_stay_within(authority):
701 failures.append("internal staged-authority symlink was refused")
702 outside = root / "outside-link-target"
703 outside.write_text("external\n", encoding="ascii")
704 (authority / "escaping").symlink_to(outside)
705 if _links_stay_within(authority):
706 failures.append("escaping staged-authority symlink was accepted")
707 return failures
708
709
710def _probe_selftest(root: Path) -> list[str]:
711 """Prove missing/stale drift and unsafe metadata remain distinct."""
712 failures: list[str] = []
713 stage = root / "probe-stage"
714 missing = _run_shell("\n".join(["set -euo pipefail", *stage_probe_lines("0" * 64, str(stage))]))
715 if missing.returncode != REMOTE_APPLY_REQUIRED_STATUS:
716 failures.append("missing WSL stage was not classified apply-required")
717 _write_owner(stage, STAGE_OWNER)
718 for member in STAGE_MEMBERS:
719 target = stage / member
720 if member in STAGE_DIRECTORY_MEMBERS:
721 target.mkdir(parents=True, exist_ok=True)
722 else:
723 target.parent.mkdir(parents=True, exist_ok=True)
724 target.write_text(f"fixture:{member}\n", encoding="ascii")
725 target.chmod(0o644)
726 generation = stage_generation(stage)
727 marker = stage / GENERATION_FILE
728 marker.write_text(f"{generation}\n", encoding="ascii")
729 marker.chmod(ROOT_READ_MODE)
730 probe = "\n".join(["set -euo pipefail", *stage_probe_lines(generation, str(stage))])
731 if _run_shell(probe).returncode:
732 failures.append("matching authenticated WSL generation was refused")
733 marker.chmod(0o644)
734 marker.write_text(f"{'1' * SHA256_HEX_LENGTH}\n", encoding="ascii")
735 marker.chmod(ROOT_READ_MODE)
736 if _run_shell(probe).returncode != REMOTE_APPLY_REQUIRED_STATUS:
737 failures.append("stale WSL generation was not classified apply-required")
738 marker.chmod(0o644)
739 marker.write_text(f"{generation}\n", encoding="ascii")
740 marker.chmod(0o666)
741 if _run_shell(probe).returncode != 1:
742 failures.append("unsafe WSL generation metadata was classified as drift")
743 return failures
744
745
746def _transaction_lock_selftest(root: Path) -> list[str]:
747 """Prove readers coexist, exclude writers, and leave no check residue."""
748 failures: list[str] = []
749 lock_root = root / "lock"
750 lock_root.mkdir(mode=0o755)
751 reader_lines = transaction_lock_lines(
752 exclusive=False, lock_root=str(lock_root), owner_uid=os.getuid()
753 )
754 reader_script = "\n".join(["set -euo pipefail", *reader_lines, "echo READY", "read -r _"])
755 holder = subprocess.Popen( # noqa: S603 -- fixed Bash runs generated offline selftest
756 ["/bin/bash", "-c", reader_script],
757 stdin=subprocess.PIPE,
758 stdout=subprocess.PIPE,
759 stderr=subprocess.PIPE,
760 text=True,
761 )
762 if holder.stdout is None or holder.stdin is None:
763 holder.kill()
764 holder.wait(timeout=5)
765 return ["WSL reader lock selftest did not create its observation pipes"]
766 if holder.stdout.readline().strip() != "READY":
767 failures.append("WSL reader lock did not acquire")
768 writer_lines = transaction_lock_lines(
769 exclusive=True, lock_root=str(lock_root), owner_uid=os.getuid()
770 )
771 writer_lines[-1] = "/usr/bin/flock -xn 9"
772 if _run_shell("\n".join(["set -euo pipefail", *writer_lines])).returncode == 0:
773 failures.append("WSL writer entered while a reader held the generation")
774 holder.terminate()
775 holder.wait(timeout=5)
776 if _run_shell("\n".join(["set -euo pipefail", *writer_lines])).returncode:
777 failures.append("WSL writer did not enter after readers exited")
778 if any(lock_root.iterdir()):
779 failures.append("WSL check lock left durable residue")
780 if holder.returncode == 0:
781 failures.append("killed WSL check did not terminate its lock holder")
782 sticky_root = root / "sticky-lock"
783 sticky_root.mkdir()
784 sticky_root.chmod(0o1777)
785 sticky_lines = transaction_lock_lines(
786 exclusive=True, lock_root=str(sticky_root), owner_uid=os.getuid()
787 )
788 sticky_lines[-1] = "/usr/bin/flock -xn 9"
789 if _run_shell("\n".join(["set -euo pipefail", *sticky_lines])).returncode:
790 failures.append("root-owned sticky WSL lock authority was refused")
791 return failures
792
793
794def _cache_receiver_selftest() -> list[str]:
795 """Prove the receiver is exclusive and inert to the Windows command shell."""
796 expected = (
797 "wsl -d Ubuntu -u root -e /usr/bin/env -i HOME=/root PATH=/usr/bin:/bin "
798 "/usr/bin/dd of=/opt/ra8-infra-cache/ra8-ci-runner.tar.part "
799 "bs=4M conv=fsync,excl status=none"
800 )
801 if cache_receive_command("Ubuntu") != expected:
802 return ["WSL runner-image receiver argv drifted"]
803 try:
804 cache_receive_command("Ubuntu&forged")
805 except ValueError:
806 return []
807 return ["WSL runner-image receiver accepted Windows command syntax"]
808
809
810def run_selftest() -> list[str]:
811 """Exercise offline ownership and atomic-publication boundaries."""
812 with tempfile.TemporaryDirectory(prefix="ra8-wsl-stage-") as raw:
813 root = Path(raw)
814 failures = (
815 _stage_selftest(root)
816 + _cache_selftest(root)
817 + _link_selftest(root)
818 + _probe_selftest(root)
819 + _transaction_lock_selftest(root)
820 + _cache_receiver_selftest()
821 )
822 if _bootstrap_action("apply", installed=False) != "--ensure":
823 failures.append("mutable operator apply lost its authenticated ensure path")
824 if _bootstrap_action("apply", installed=True) != "--verify-cache":
825 failures.append("installed WSL apply attempted to mutate root-owned uv inputs")
826 if _bootstrap_action("check", installed=False) != "--verify-cache":
827 failures.append("WSL check attempted to mutate uv inputs")
828 return failures