ci: preserve Apple Recovery daemon during read-only readiness probe
PR and Push Build/Test / build-and-test (push) Canceled after 0s
PR and Push Build/Test / portable-build-and-test (push) Canceled after 0s

This commit is contained in:
dh
2026-10-03 20:17:56 +02:00
parent 40281b57a5
commit 4606de0696
3 changed files with 25 additions and 6 deletions
+15 -3
View File
@@ -168,6 +168,8 @@ static class NativeDiagnostic
static void ValidateContracts()
{
if (Hash(File.ReadAllBytes(Path.Combine("tools", "ci", "macos-native-readiness.sh"))) != "4d428f594dac14eff64ed87b172c81ecf85ac91da8c5460cd6ec4b1d310800c3")
throw new InvalidOperationException("Bootstrap A/B requires the unchanged 40281b readiness probe and limits.");
XDocument.Parse(DiagnosticDaemon);
if (Encoding.UTF8.GetByteCount(DiagnosticDaemon) > Encoding.UTF8.GetByteCount(OriginalDaemon + "\n")) throw new InvalidOperationException("Daemon replacement exceeds original file.");
var good = JsonSerializer.Serialize(new { token = "validation", success = true, osVersion = "14.6.1", architecture = "x86_64", uid = 0, disk = "/dev/disk1", diskBytes = GuestDiskBytes, readOnly = false, systemExit = 0, diskArbitrationExit = 0, recoveryExit = 0, diskListExit = 0 });
@@ -199,16 +201,26 @@ static class NativeDiagnostic
var entry = ReplaceOnce(File.ReadAllText(entryPath), "set -Eeuo pipefail\n", "set -Eeuo pipefail\n\n# Diagnostic budget: inspect existing Docker storage before Recovery download/boot.\ndf -Pk /storage\nfree_kib=$(df -Pk /storage | awk 'NR==2 {print $4}')\n[[ \"$free_kib\" =~ ^[0-9]+$ ]] && (( free_kib >= 8 * 1024 * 1024 )) || { echo 'Existing Docker storage has less than the 8-GiB diagnostic budget.' >&2; exit 1; }\n");
var hookPath = Path.Combine("tools", "ci", "macos-native-readiness.sh");
var hook = ReplaceOnce(File.ReadAllText(hookPath), "@@PROOF_TOKEN@@", token);
foreach (var pair in new[] { ("recovery-patch.py", patch), ("Dockerfile.patched", dockerfile), ("container-entry.sh", entry), ("guest-launch.sh", hook), ("recoveryosd-original.plist", daemon), ("recoveryosd-diagnostic.plist", DiagnosticDaemon), ("early-bootstrap.sh", MountOnlyBootstrap) })
var wrapper = File.ReadAllText(Path.Combine("tools", "ci", "macos-native-bootstrap.sh"));
var imagePath = Path.Combine(source, "src", "image.sh");
var originalImage = File.ReadAllText(imagePath);
if (Hash(Encoding.UTF8.GetBytes(originalImage)) != "c08bf9436fb8b72ea82fdf0e677641ab2fc42a0a59e2cf0309c00df519884c5c") throw new InvalidOperationException("Pinned Recovery staging script hash mismatch.");
var image = ReplaceOnce(originalImage, " if ! cp -f \"$IMAGE_TOOLS/recovery/launch.sh\" \"$script\"; then\n", " if ! cp -f \"$IMAGE_TOOLS/recovery/launch.sh\" \"$script\" ||\n ! cp -f \"$IMAGE_TOOLS/recovery/readiness.sh\" \"${script%/*}/readiness.sh\"; then\n");
image = ReplaceOnce(image, " if ! cmp -s \"$IMAGE_TOOLS/recovery/launch.sh\" \"$script\" ||\n", " if ! cmp -s \"$IMAGE_TOOLS/recovery/launch.sh\" \"$script\" ||\n ! cmp -s \"$IMAGE_TOOLS/recovery/readiness.sh\" \"$state/readiness.sh\" ||\n");
foreach (var pair in new[] { ("recovery-patch.py", patch), ("Dockerfile.patched", dockerfile), ("container-entry.sh", entry), ("guest-launch.sh", wrapper), ("guest-readiness.sh", hook), ("image.sh.patched", image), ("recoveryosd-original.plist", daemon), ("recoveryosd-diagnostic.plist", DiagnosticDaemon), ("early-bootstrap.sh", MountOnlyBootstrap) })
File.WriteAllText(Path.Combine(output, pair.Item1), pair.Item2, new UTF8Encoding(false));
Save(Path.Combine(output, "source-hashes.json"), Directory.GetFiles(output).Where(path => Path.GetFileName(path) is "recovery-patch.py" or "Dockerfile.patched" or "container-entry.sh" or "guest-launch.sh" or "recoveryosd-original.plist" or "recoveryosd-diagnostic.plist" or "early-bootstrap.sh").ToDictionary(path => Path.GetFileName(path)!, path => Hash(File.ReadAllBytes(path))));
Save(Path.Combine(output, "source-hashes.json"), Directory.GetFiles(output).Where(path => Path.GetFileName(path) is "recovery-patch.py" or "Dockerfile.patched" or "container-entry.sh" or "guest-launch.sh" or "guest-readiness.sh" or "image.sh.patched" or "recoveryosd-original.plist" or "recoveryosd-diagnostic.plist" or "early-bootstrap.sh").ToDictionary(path => Path.GetFileName(path)!, path => Hash(File.ReadAllBytes(path))));
await Command("bash", ["-n", Path.Combine(output, "guest-launch.sh")], output, "guest-hook-syntax", cancellation);
await Command("bash", ["-n", Path.Combine(output, "guest-readiness.sh")], output, "guest-readiness-syntax", cancellation);
await Command("bash", ["-n", Path.Combine(output, "image.sh.patched")], output, "guest-staging-syntax", cancellation);
await Command("bash", ["-n", Path.Combine(output, "container-entry.sh")], output, "entry-syntax", cancellation);
if (!writeSource) return;
File.WriteAllText(patchPath, patch, new UTF8Encoding(false));
File.WriteAllText(dockerPath, dockerfile, new UTF8Encoding(false));
File.WriteAllText(entryPath, entry, new UTF8Encoding(false));
File.WriteAllText(Path.Combine(source, "src/install/recovery/launch.sh"), hook, new UTF8Encoding(false));
File.WriteAllText(imagePath, image, new UTF8Encoding(false));
File.WriteAllText(Path.Combine(source, "src/install/recovery/launch.sh"), wrapper, new UTF8Encoding(false));
File.WriteAllText(Path.Combine(source, "src/install/recovery/readiness.sh"), hook, new UTF8Encoding(false));
}
static string ReplaceOnce(string text, string oldValue, string newValue) => ReplaceAllExact(text, oldValue, newValue, 1);