Fix first-run setup timing out on the kernel download

On a Mac without the Linux kernel, setup ran `container system start
--enable-kernel-install` with a 120 s limit. That command downloads a
697 MB archive without printing anything, so most connections hit the
limit and the window showed `timedOut([...])`.

- Start the service with --disable-kernel-install (10 min: a first
  start still pulls the 69 MB base filesystem).
- Download the kernel in its own step with progress, then hand the file
  to `container system kernel set --tar ... --digest ...`, which checks
  it against the runtime's digest.
- Opening the setup window no longer starts the download; Set Up does.
- Timeouts and runner errors read as sentences.
- A rebuild deletes the image it replaced, which frees about 2 GB each
  time, and deletes or stops the builder VM unless it was already
  running (it kept about 2.8 GB of memory).
- Setup asks for 10 GB free, measured on a real setup.
This commit is contained in:
aculix committed 2026-09-29 16:25:50 +05:30
1 parent 4b67728cce
commit 9fd654298a
13 files changed
+559 -128

No files matched your search

@@ -557,7 +557,7 @@ public final class SessionController {
}
}
/// No VM exists yet, so there is nothing to destroy. Starting the service needs no password, so it just happens.
/// No VM exists yet, so there is nothing to destroy. Starting the service or fetching its kernel needs no password, so it just happens.
private func needsSetup(_ issue: SetupIssue, for session: Session) {
session.retired = true
liveIDs.remove(session.id)
@@ -752,11 +752,12 @@ public final class SessionController {
if mode == .link || pendingURL != nil { transition(to: card) } else { enterIdle() }
}
/// A check only starts the service: the kernel is a 700 MB download, so it waits for Set Up.
private static func fixes(_ issue: SetupIssue, in mode: RunMode) -> Bool {
switch (mode, issue) {
case (.window(continuing: true), .serviceStopped), (.window(continuing: true), .kernelMissing),
(.window(continuing: true), .imageMissing), (.window(continuing: true), .imageOutdated),
(.check, .serviceStopped), (.check, .kernelMissing):
(.check, .serviceStopped):
true
default:
false
@@ -767,7 +768,6 @@ public final class SessionController {
private static func sameFix(_ a: SetupIssue, _ b: SetupIssue) -> Bool {
func group(_ issue: SetupIssue) -> Int? {
switch issue {
case .serviceStopped, .kernelMissing: 0
case .imageMissing, .imageOutdated, .imageStale: 1
default: nil
}
@@ -925,6 +925,9 @@ public final class SessionController {
case StreamError.protocolViolation(let detail): "The VM sent a malformed message: \(detail)."
case StreamError.timedOut: "The VM didn't answer in time."
case StreamError.refused: "Couldn't connect to the VM."
case CLIRunner.Failure.timedOut: "The container runtime stopped responding. Try again."
case CLIRunner.Failure.notInstalled: "Apple's container runtime isn't installed."
case CLIRunner.Failure.launchFailed(let detail): "Couldn't run the container runtime: \(detail)"
default: String(describing: error)
}
}