Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Package.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -227,6 +227,7 @@ let package = Package(
name: "ContainerAPIServiceTests",
dependencies: [
.product(name: "Containerization", package: "containerization"),
"ContainerAPIService",
"ContainerResource",
"ContainerRuntimeLinuxClient",
"ContainerRuntimeClient",
Expand Down
55 changes: 46 additions & 9 deletions Sources/ContainerCommands/System/Kernel/KernelSet.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,18 +47,25 @@ extension Application {
@Option(name: .customLong("tar"), help: "Filesystem path or remote URL to a tar archive containing a kernel file")
var tarPath: String? = nil

@Option(name: .long, help: "Expected digest for the tar archive, for example sha256:<hex>. Required when --tar is a remote URL.")
var digest: String? = nil

@OptionGroup
public var logOptions: Flags.Logging

public init() {}

public func run() async throws {
let containerSystemConfig: ContainerSystemConfig = try await Application.loadContainerSystemConfig()
if recommended {
let containerSystemConfig: ContainerSystemConfig = try await Application.loadContainerSystemConfig()
let url = containerSystemConfig.kernel.url
let path: String = containerSystemConfig.kernel.binaryPath
log.info("Installing the recommended kernel from \(url)...")
try await Self.downloadAndInstallWithProgressBar(tarRemoteURL: url, kernelFilePath: path, force: force)
try await Self.downloadAndInstallWithProgressBar(
tarRemoteURL: url,
kernelFilePath: path,
expectedDigest: containerSystemConfig.kernel.digest,
force: force)
return
}
guard tarPath != nil else {
Expand All@@ -68,6 +75,9 @@ extension Application {
}

private func setKernelFromBinary() async throws {
guard digest == nil else {
throw ArgumentParser.ValidationError("'--digest' can only be used with '--tar'")
}
guard let binaryPath else {
throw ArgumentParser.ValidationError("missing argument '--binary'")
}
Expand All@@ -84,16 +94,32 @@ extension Application {
throw ArgumentParser.ValidationError("missing argument '--tar")
}
let platform = try getSystemPlatform()
let remoteURL = URL(string: tarPath)
let remoteScheme = remoteURL?.scheme?.lowercased()
let isHTTPURL = remoteScheme == "http" || remoteScheme == "https"
let localTarPath = URL(fileURLWithPath: tarPath, relativeTo: .currentDirectory()).path
let fm = FileManager.default
if fm.fileExists(atPath: localTarPath) {
try await ClientKernel.installKernelFromTar(tarFile: localTarPath, kernelFilePath: binaryPath, platform: platform, force: force)
if !isHTTPURL && fm.fileExists(atPath: localTarPath) {
try await ClientKernel.installKernelFromTar(
tarFile: localTarPath,
kernelFilePath: binaryPath,
platform: platform,
expectedDigest: digest,
force: force)
return
}
guard let remoteURL = URL(string: tarPath) else {
guard let remoteURL else {
throw ContainerizationError(.invalidArgument, message: "invalid remote URL '\(tarPath)' for argument '--tar'. Missing protocol?")
}
try await Self.downloadAndInstallWithProgressBar(tarRemoteURL: remoteURL, kernelFilePath: binaryPath, platform: platform, force: force)
guard let digest else {
throw ArgumentParser.ValidationError("'--digest' is required when '--tar' is a remote URL")
}
try await Self.downloadAndInstallWithProgressBar(
tarRemoteURL: remoteURL,
kernelFilePath: binaryPath,
platform: platform,
expectedDigest: digest,
force: force)
}

private func getSystemPlatform() throws -> SystemPlatform {
Expand All@@ -107,18 +133,29 @@ extension Application {
}
}

static func downloadAndInstallWithProgressBar(tarRemoteURL: URL, kernelFilePath: String, platform: SystemPlatform = .current, force: Bool) async throws {
static func downloadAndInstallWithProgressBar(
tarRemoteURL: URL,
kernelFilePath: String,
platform: SystemPlatform = .current,
expectedDigest: String,
force: Bool
) async throws {
let progressConfig = try ProgressConfig(
showTasks: true,
totalTasks: 2
totalTasks: 3
)
let progress = ProgressBar(config: progressConfig)
defer {
progress.finish()
}
progress.start()
try await ClientKernel.installKernelFromTar(
tarFile: tarRemoteURL.absoluteString, kernelFilePath: kernelFilePath, platform: platform, progressUpdate: progress.handler, force: force)
tarFile: tarRemoteURL.absoluteString,
kernelFilePath: kernelFilePath,
platform: platform,
progressUpdate: progress.handler,
expectedDigest: expectedDigest,
force: force)
progress.finish()
}

Expand Down
13 changes: 10 additions & 3 deletions Sources/ContainerCommands/System/SystemStart.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -157,7 +157,10 @@ extension Application {
guard await !kernelExists() else {
return
}
try await installDefaultKernel(kernelURL: containerSystemConfig.kernel.url, kernelBinaryPath: containerSystemConfig.kernel.binaryPath)
try await installDefaultKernel(
kernelURL: containerSystemConfig.kernel.url,
kernelBinaryPath: containerSystemConfig.kernel.binaryPath,
kernelDigest: containerSystemConfig.kernel.digest)
}

private func installInitialFilesystem(initImage: String) async throws {
Expand All@@ -171,7 +174,7 @@ extension Application {
}
}

private func installDefaultKernel(kernelURL: URL, kernelBinaryPath: String) async throws {
private func installDefaultKernel(kernelURL: URL, kernelBinaryPath: String, kernelDigest: String) async throws {
var shouldInstallKernel = false
if kernelInstall == nil {
print("No default kernel configured.")
Expand All@@ -191,7 +194,11 @@ extension Application {
return
}
log.info("Installing kernel...")
try await KernelSet.downloadAndInstallWithProgressBar(tarRemoteURL: kernelURL, kernelFilePath: kernelBinaryPath, force: true)
try await KernelSet.downloadAndInstallWithProgressBar(
tarRemoteURL: kernelURL,
kernelFilePath: kernelBinaryPath,
expectedDigest: kernelDigest,
force: true)
}

private func initImageExists(containerSystemConfig: ContainerSystemConfig) async -> Bool {
Expand Down
30 changes: 23 additions & 7 deletions Sources/ContainerPersistence/ContainerSystemConfig.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -168,35 +168,50 @@ final public class KernelConfig: Codable, Sendable {
public static let defaultBinaryPath = "opt/kata/share/kata-containers/vmlinux-6.18.15-186"
public static let defaultURL: URL =
URL(string: "https://github.com/kata-containers/kata-containers/releases/download/3.28.0/kata-static-3.28.0-arm64.tar.zst")!
public static let defaultDigest = "sha256:f63d54507d1f18635d94475077e4c2330de4d8e05cedf25f7c38f063b0e66a91"

private enum CodingKeys: String, CodingKey {
case binaryPath
case url
case digest
}

public let binaryPath: String
public let url: URL
public let digest: String

public init(
binaryPath: String = defaultBinaryPath,
url: URL = defaultURL
) {
public init(binaryPath: String = defaultBinaryPath, url: URL = defaultURL, digest: String = defaultDigest) {
self.binaryPath = binaryPath
self.url = url
self.digest = digest
}

public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.binaryPath =
try container.decodeIfPresent(String.self, forKey: .binaryPath)
?? Self.defaultBinaryPath
if let urlString = try container.decodeIfPresent(String.self, forKey: .url),
let parsed = URL(string: urlString)
{
if let urlString = try container.decodeIfPresent(String.self, forKey: .url) {
guard let parsed = URL(string: urlString) else {
throw DecodingError.dataCorruptedError(
forKey: .url,
in: container,
debugDescription: "invalid kernel URL '\(urlString)'")
}
self.url = parsed
} else {
self.url = Self.defaultURL
}
if let digest = try container.decodeIfPresent(String.self, forKey: .digest) {
self.digest = digest
} else if self.url.absoluteString == Self.defaultURL.absoluteString {
self.digest = Self.defaultDigest
} else {
throw DecodingError.dataCorruptedError(
forKey: .digest,
in: container,
debugDescription: "kernel.digest is required when kernel.url is not the default URL")
}
}

// JSONEncoder special-cases URL to encode as absoluteString, but third-party
Expand All@@ -209,6 +224,7 @@ final public class KernelConfig: Codable, Sendable {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(binaryPath, forKey: .binaryPath)
try container.encode(url.absoluteString, forKey: .url)
try container.encode(digest, forKey: .digest)
}
}

Expand Down
14 changes: 11 additions & 3 deletions Sources/Services/ContainerAPIService/Client/ClientKernel.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,15 +42,23 @@ extension ClientKernel {
try await client.send(message)
}

public static func installKernelFromTar(tarFile: String, kernelFilePath: String, platform: SystemPlatform, progressUpdate: ProgressUpdateHandler? = nil, force: Bool)
async throws
{
public static func installKernelFromTar(
tarFile: String,
kernelFilePath: String,
platform: SystemPlatform,
progressUpdate: ProgressUpdateHandler? = nil,
expectedDigest: String? = nil,
force: Bool
) async throws {
let client = newClient()
let message = XPCMessage(route: .installKernel)

message.set(key: .kernelTarURL, value: tarFile)
message.set(key: .kernelFilePath, value: kernelFilePath)
message.set(key: .kernelForce, value: force)
if let expectedDigest {
message.set(key: .kernelDigest, value: expectedDigest)
}

let platformData = try JSONEncoder().encode(platform)
message.set(key: .systemPlatform, value: platformData)
Expand Down
1 change: 1 addition & 0 deletions Sources/Services/ContainerAPIService/Client/XPC+.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -112,6 +112,7 @@ public enum XPCKeys: String {
case kernelFilePath
case systemPlatform
case kernelForce
case kernelDigest

/// Init image reference
case initImage
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,7 @@ public struct KernelHarness: Sendable {
let kernelFilePath = try message.kernelFilePath()
let platform = try message.platform()
let force = try message.kernelForce()
let expectedDigest = message.kernelDigest()

guard let kernelTarUrl = try message.kernelTarURL() else {
// We have been given a path to a kernel binary on disk
Expand All@@ -47,7 +48,12 @@ public struct KernelHarness: Sendable {

let progressUpdateService = ProgressUpdateService(message: message)
try await self.service.installKernelFrom(
tar: kernelTarUrl, kernelFilePath: kernelFilePath, platform: platform, progressUpdate: progressUpdateService?.handler, force: force)
tar: kernelTarUrl,
kernelFilePath: kernelFilePath,
platform: platform,
progressUpdate: progressUpdateService?.handler,
expectedDigest: expectedDigest,
force: force)
return message.reply()
}

Expand DownExpand Up@@ -85,13 +91,17 @@ extension XPCMessage {
guard let kernelTarURLString = self.string(key: .kernelTarURL) else {
return nil
}
guard let k = URL(string: kernelTarURLString) else {
throw ContainerizationError(.invalidArgument, message: "cannot parse URL from \(kernelTarURLString)")
if let k = URL(string: kernelTarURLString), k.scheme != nil {
return k
}
return k
return URL(fileURLWithPath: kernelTarURLString)
}

fileprivate func kernelForce() throws -> Bool {
self.bool(key: .kernelForce)
}

fileprivate func kernelDigest() -> String? {
self.string(key: .kernelDigest)
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Package.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -227,6 +227,7 @@ let package = Package(
name: "ContainerAPIServiceTests",
dependencies: [
.product(name: "Containerization", package: "containerization"),
"ContainerAPIService",
"ContainerResource",
"ContainerRuntimeLinuxClient",
"ContainerRuntimeClient",
Expand Down
55 changes: 46 additions & 9 deletions Sources/ContainerCommands/System/Kernel/KernelSet.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,18 +47,25 @@ extension Application {
@Option(name: .customLong("tar"), help: "Filesystem path or remote URL to a tar archive containing a kernel file")
var tarPath: String? = nil

@Option(name: .long, help: "Expected digest for the tar archive, for example sha256:<hex>. Required when --tar is a remote URL.")
var digest: String? = nil

@OptionGroup
public var logOptions: Flags.Logging

public init() {}

public func run() async throws {
let containerSystemConfig: ContainerSystemConfig = try await Application.loadContainerSystemConfig()
if recommended {
let containerSystemConfig: ContainerSystemConfig = try await Application.loadContainerSystemConfig()
let url = containerSystemConfig.kernel.url
let path: String = containerSystemConfig.kernel.binaryPath
log.info("Installing the recommended kernel from \(url)...")
try await Self.downloadAndInstallWithProgressBar(tarRemoteURL: url, kernelFilePath: path, force: force)
try await Self.downloadAndInstallWithProgressBar(
tarRemoteURL: url,
kernelFilePath: path,
expectedDigest: containerSystemConfig.kernel.digest,
force: force)
return
}
guard tarPath != nil else {
Expand All@@ -68,6 +75,9 @@ extension Application {
}

private func setKernelFromBinary() async throws {
guard digest == nil else {
throw ArgumentParser.ValidationError("'--digest' can only be used with '--tar'")
}
guard let binaryPath else {
throw ArgumentParser.ValidationError("missing argument '--binary'")
}
Expand All@@ -84,16 +94,32 @@ extension Application {
throw ArgumentParser.ValidationError("missing argument '--tar")
}
let platform = try getSystemPlatform()
let remoteURL = URL(string: tarPath)
let remoteScheme = remoteURL?.scheme?.lowercased()
let isHTTPURL = remoteScheme == "http" || remoteScheme == "https"
let localTarPath = URL(fileURLWithPath: tarPath, relativeTo: .currentDirectory()).path
let fm = FileManager.default
if fm.fileExists(atPath: localTarPath) {
try await ClientKernel.installKernelFromTar(tarFile: localTarPath, kernelFilePath: binaryPath, platform: platform, force: force)
if !isHTTPURL && fm.fileExists(atPath: localTarPath) {
try await ClientKernel.installKernelFromTar(
tarFile: localTarPath,
kernelFilePath: binaryPath,
platform: platform,
expectedDigest: digest,
force: force)
return
}
guard let remoteURL = URL(string: tarPath) else {
guard let remoteURL else {
throw ContainerizationError(.invalidArgument, message: "invalid remote URL '\(tarPath)' for argument '--tar'. Missing protocol?")
}
try await Self.downloadAndInstallWithProgressBar(tarRemoteURL: remoteURL, kernelFilePath: binaryPath, platform: platform, force: force)
guard let digest else {
throw ArgumentParser.ValidationError("'--digest' is required when '--tar' is a remote URL")
}
try await Self.downloadAndInstallWithProgressBar(
tarRemoteURL: remoteURL,
kernelFilePath: binaryPath,
platform: platform,
expectedDigest: digest,
force: force)
}

private func getSystemPlatform() throws -> SystemPlatform {
Expand All@@ -107,18 +133,29 @@ extension Application {
}
}

static func downloadAndInstallWithProgressBar(tarRemoteURL: URL, kernelFilePath: String, platform: SystemPlatform = .current, force: Bool) async throws {
static func downloadAndInstallWithProgressBar(
tarRemoteURL: URL,
kernelFilePath: String,
platform: SystemPlatform = .current,
expectedDigest: String,
force: Bool
) async throws {
let progressConfig = try ProgressConfig(
showTasks: true,
totalTasks: 2
totalTasks: 3
)
let progress = ProgressBar(config: progressConfig)
defer {
progress.finish()
}
progress.start()
try await ClientKernel.installKernelFromTar(
tarFile: tarRemoteURL.absoluteString, kernelFilePath: kernelFilePath, platform: platform, progressUpdate: progress.handler, force: force)
tarFile: tarRemoteURL.absoluteString,
kernelFilePath: kernelFilePath,
platform: platform,
progressUpdate: progress.handler,
expectedDigest: expectedDigest,
force: force)
progress.finish()
}

Expand Down
13 changes: 10 additions & 3 deletions Sources/ContainerCommands/System/SystemStart.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -157,7 +157,10 @@ extension Application {
guard await !kernelExists() else {
return
}
try await installDefaultKernel(kernelURL: containerSystemConfig.kernel.url, kernelBinaryPath: containerSystemConfig.kernel.binaryPath)
try await installDefaultKernel(
kernelURL: containerSystemConfig.kernel.url,
kernelBinaryPath: containerSystemConfig.kernel.binaryPath,
kernelDigest: containerSystemConfig.kernel.digest)
}

private func installInitialFilesystem(initImage: String) async throws {
Expand All@@ -171,7 +174,7 @@ extension Application {
}
}

private func installDefaultKernel(kernelURL: URL, kernelBinaryPath: String) async throws {
private func installDefaultKernel(kernelURL: URL, kernelBinaryPath: String, kernelDigest: String) async throws {
var shouldInstallKernel = false
if kernelInstall == nil {
print("No default kernel configured.")
Expand All@@ -191,7 +194,11 @@ extension Application {
return
}
log.info("Installing kernel...")
try await KernelSet.downloadAndInstallWithProgressBar(tarRemoteURL: kernelURL, kernelFilePath: kernelBinaryPath, force: true)
try await KernelSet.downloadAndInstallWithProgressBar(
tarRemoteURL: kernelURL,
kernelFilePath: kernelBinaryPath,
expectedDigest: kernelDigest,
force: true)
}

private func initImageExists(containerSystemConfig: ContainerSystemConfig) async -> Bool {
Expand Down
30 changes: 23 additions & 7 deletions Sources/ContainerPersistence/ContainerSystemConfig.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -168,35 +168,50 @@ final public class KernelConfig: Codable, Sendable {
public static let defaultBinaryPath = "opt/kata/share/kata-containers/vmlinux-6.18.15-186"
public static let defaultURL: URL =
URL(string: "https://github.com/kata-containers/kata-containers/releases/download/3.28.0/kata-static-3.28.0-arm64.tar.zst")!
public static let defaultDigest = "sha256:f63d54507d1f18635d94475077e4c2330de4d8e05cedf25f7c38f063b0e66a91"

private enum CodingKeys: String, CodingKey {
case binaryPath
case url
case digest
}

public let binaryPath: String
public let url: URL
public let digest: String

public init(
binaryPath: String = defaultBinaryPath,
url: URL = defaultURL
) {
public init(binaryPath: String = defaultBinaryPath, url: URL = defaultURL, digest: String = defaultDigest) {
self.binaryPath = binaryPath
self.url = url
self.digest = digest
}

public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.binaryPath =
try container.decodeIfPresent(String.self, forKey: .binaryPath)
?? Self.defaultBinaryPath
if let urlString = try container.decodeIfPresent(String.self, forKey: .url),
let parsed = URL(string: urlString)
{
if let urlString = try container.decodeIfPresent(String.self, forKey: .url) {
guard let parsed = URL(string: urlString) else {
throw DecodingError.dataCorruptedError(
forKey: .url,
in: container,
debugDescription: "invalid kernel URL '\(urlString)'")
}
self.url = parsed
} else {
self.url = Self.defaultURL
}
if let digest = try container.decodeIfPresent(String.self, forKey: .digest) {
self.digest = digest
} else if self.url.absoluteString == Self.defaultURL.absoluteString {
self.digest = Self.defaultDigest
} else {
throw DecodingError.dataCorruptedError(
forKey: .digest,
in: container,
debugDescription: "kernel.digest is required when kernel.url is not the default URL")
}
}

// JSONEncoder special-cases URL to encode as absoluteString, but third-party
Expand All@@ -209,6 +224,7 @@ final public class KernelConfig: Codable, Sendable {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(binaryPath, forKey: .binaryPath)
try container.encode(url.absoluteString, forKey: .url)
try container.encode(digest, forKey: .digest)
}
}

Expand Down
14 changes: 11 additions & 3 deletions Sources/Services/ContainerAPIService/Client/ClientKernel.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,15 +42,23 @@ extension ClientKernel {
try await client.send(message)
}

public static func installKernelFromTar(tarFile: String, kernelFilePath: String, platform: SystemPlatform, progressUpdate: ProgressUpdateHandler? = nil, force: Bool)
async throws
{
public static func installKernelFromTar(
tarFile: String,
kernelFilePath: String,
platform: SystemPlatform,
progressUpdate: ProgressUpdateHandler? = nil,
expectedDigest: String? = nil,
force: Bool
) async throws {
let client = newClient()
let message = XPCMessage(route: .installKernel)

message.set(key: .kernelTarURL, value: tarFile)
message.set(key: .kernelFilePath, value: kernelFilePath)
message.set(key: .kernelForce, value: force)
if let expectedDigest {
message.set(key: .kernelDigest, value: expectedDigest)
}

let platformData = try JSONEncoder().encode(platform)
message.set(key: .systemPlatform, value: platformData)
Expand Down
1 change: 1 addition & 0 deletions Sources/Services/ContainerAPIService/Client/XPC+.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -112,6 +112,7 @@ public enum XPCKeys: String {
case kernelFilePath
case systemPlatform
case kernelForce
case kernelDigest

/// Init image reference
case initImage
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,7 @@ public struct KernelHarness: Sendable {
let kernelFilePath = try message.kernelFilePath()
let platform = try message.platform()
let force = try message.kernelForce()
let expectedDigest = message.kernelDigest()

guard let kernelTarUrl = try message.kernelTarURL() else {
// We have been given a path to a kernel binary on disk
Expand All@@ -47,7 +48,12 @@ public struct KernelHarness: Sendable {

let progressUpdateService = ProgressUpdateService(message: message)
try await self.service.installKernelFrom(
tar: kernelTarUrl, kernelFilePath: kernelFilePath, platform: platform, progressUpdate: progressUpdateService?.handler, force: force)
tar: kernelTarUrl,
kernelFilePath: kernelFilePath,
platform: platform,
progressUpdate: progressUpdateService?.handler,
expectedDigest: expectedDigest,
force: force)
return message.reply()
}

Expand DownExpand Up@@ -85,13 +91,17 @@ extension XPCMessage {
guard let kernelTarURLString = self.string(key: .kernelTarURL) else {
return nil
}
guard let k = URL(string: kernelTarURLString) else {
throw ContainerizationError(.invalidArgument, message: "cannot parse URL from \(kernelTarURLString)")
if let k = URL(string: kernelTarURLString), k.scheme != nil {
return k
}
return k
return URL(fileURLWithPath: kernelTarURLString)
}

fileprivate func kernelForce() throws -> Bool {
self.bool(key: .kernelForce)
}

fileprivate func kernelDigest() -> String? {
self.string(key: .kernelDigest)
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Package.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -227,6 +227,7 @@ let package = Package(
name: "ContainerAPIServiceTests",
dependencies: [
.product(name: "Containerization", package: "containerization"),
"ContainerAPIService",
"ContainerResource",
"ContainerRuntimeLinuxClient",
"ContainerRuntimeClient",
Expand Down
55 changes: 46 additions & 9 deletions Sources/ContainerCommands/System/Kernel/KernelSet.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,18 +47,25 @@ extension Application {
@Option(name: .customLong("tar"), help: "Filesystem path or remote URL to a tar archive containing a kernel file")
var tarPath: String? = nil

@Option(name: .long, help: "Expected digest for the tar archive, for example sha256:<hex>. Required when --tar is a remote URL.")
var digest: String? = nil

@OptionGroup
public var logOptions: Flags.Logging

public init() {}

public func run() async throws {
let containerSystemConfig: ContainerSystemConfig = try await Application.loadContainerSystemConfig()
if recommended {
let containerSystemConfig: ContainerSystemConfig = try await Application.loadContainerSystemConfig()
let url = containerSystemConfig.kernel.url
let path: String = containerSystemConfig.kernel.binaryPath
log.info("Installing the recommended kernel from \(url)...")
try await Self.downloadAndInstallWithProgressBar(tarRemoteURL: url, kernelFilePath: path, force: force)
try await Self.downloadAndInstallWithProgressBar(
tarRemoteURL: url,
kernelFilePath: path,
expectedDigest: containerSystemConfig.kernel.digest,
force: force)
return
}
guard tarPath != nil else {
Expand All@@ -68,6 +75,9 @@ extension Application {
}

private func setKernelFromBinary() async throws {
guard digest == nil else {
throw ArgumentParser.ValidationError("'--digest' can only be used with '--tar'")
}
guard let binaryPath else {
throw ArgumentParser.ValidationError("missing argument '--binary'")
}
Expand All@@ -84,16 +94,32 @@ extension Application {
throw ArgumentParser.ValidationError("missing argument '--tar")
}
let platform = try getSystemPlatform()
let remoteURL = URL(string: tarPath)
let remoteScheme = remoteURL?.scheme?.lowercased()
let isHTTPURL = remoteScheme == "http" || remoteScheme == "https"
let localTarPath = URL(fileURLWithPath: tarPath, relativeTo: .currentDirectory()).path
let fm = FileManager.default
if fm.fileExists(atPath: localTarPath) {
try await ClientKernel.installKernelFromTar(tarFile: localTarPath, kernelFilePath: binaryPath, platform: platform, force: force)
if !isHTTPURL && fm.fileExists(atPath: localTarPath) {
try await ClientKernel.installKernelFromTar(
tarFile: localTarPath,
kernelFilePath: binaryPath,
platform: platform,
expectedDigest: digest,
force: force)
return
}
guard let remoteURL = URL(string: tarPath) else {
guard let remoteURL else {
throw ContainerizationError(.invalidArgument, message: "invalid remote URL '\(tarPath)' for argument '--tar'. Missing protocol?")
}
try await Self.downloadAndInstallWithProgressBar(tarRemoteURL: remoteURL, kernelFilePath: binaryPath, platform: platform, force: force)
guard let digest else {
throw ArgumentParser.ValidationError("'--digest' is required when '--tar' is a remote URL")
}
try await Self.downloadAndInstallWithProgressBar(
tarRemoteURL: remoteURL,
kernelFilePath: binaryPath,
platform: platform,
expectedDigest: digest,
force: force)
}

private func getSystemPlatform() throws -> SystemPlatform {
Expand All@@ -107,18 +133,29 @@ extension Application {
}
}

static func downloadAndInstallWithProgressBar(tarRemoteURL: URL, kernelFilePath: String, platform: SystemPlatform = .current, force: Bool) async throws {
static func downloadAndInstallWithProgressBar(
tarRemoteURL: URL,
kernelFilePath: String,
platform: SystemPlatform = .current,
expectedDigest: String,
force: Bool
) async throws {
let progressConfig = try ProgressConfig(
showTasks: true,
totalTasks: 2
totalTasks: 3
)
let progress = ProgressBar(config: progressConfig)
defer {
progress.finish()
}
progress.start()
try await ClientKernel.installKernelFromTar(
tarFile: tarRemoteURL.absoluteString, kernelFilePath: kernelFilePath, platform: platform, progressUpdate: progress.handler, force: force)
tarFile: tarRemoteURL.absoluteString,
kernelFilePath: kernelFilePath,
platform: platform,
progressUpdate: progress.handler,
expectedDigest: expectedDigest,
force: force)
progress.finish()
}

Expand Down
13 changes: 10 additions & 3 deletions Sources/ContainerCommands/System/SystemStart.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -157,7 +157,10 @@ extension Application {
guard await !kernelExists() else {
return
}
try await installDefaultKernel(kernelURL: containerSystemConfig.kernel.url, kernelBinaryPath: containerSystemConfig.kernel.binaryPath)
try await installDefaultKernel(
kernelURL: containerSystemConfig.kernel.url,
kernelBinaryPath: containerSystemConfig.kernel.binaryPath,
kernelDigest: containerSystemConfig.kernel.digest)
}

private func installInitialFilesystem(initImage: String) async throws {
Expand All@@ -171,7 +174,7 @@ extension Application {
}
}

private func installDefaultKernel(kernelURL: URL, kernelBinaryPath: String) async throws {
private func installDefaultKernel(kernelURL: URL, kernelBinaryPath: String, kernelDigest: String) async throws {
var shouldInstallKernel = false
if kernelInstall == nil {
print("No default kernel configured.")
Expand All@@ -191,7 +194,11 @@ extension Application {
return
}
log.info("Installing kernel...")
try await KernelSet.downloadAndInstallWithProgressBar(tarRemoteURL: kernelURL, kernelFilePath: kernelBinaryPath, force: true)
try await KernelSet.downloadAndInstallWithProgressBar(
tarRemoteURL: kernelURL,
kernelFilePath: kernelBinaryPath,
expectedDigest: kernelDigest,
force: true)
}

private func initImageExists(containerSystemConfig: ContainerSystemConfig) async -> Bool {
Expand Down
30 changes: 23 additions & 7 deletions Sources/ContainerPersistence/ContainerSystemConfig.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -168,35 +168,50 @@ final public class KernelConfig: Codable, Sendable {
public static let defaultBinaryPath = "opt/kata/share/kata-containers/vmlinux-6.18.15-186"
public static let defaultURL: URL =
URL(string: "https://github.com/kata-containers/kata-containers/releases/download/3.28.0/kata-static-3.28.0-arm64.tar.zst")!
public static let defaultDigest = "sha256:f63d54507d1f18635d94475077e4c2330de4d8e05cedf25f7c38f063b0e66a91"

private enum CodingKeys: String, CodingKey {
case binaryPath
case url
case digest
}

public let binaryPath: String
public let url: URL
public let digest: String

public init(
binaryPath: String = defaultBinaryPath,
url: URL = defaultURL
) {
public init(binaryPath: String = defaultBinaryPath, url: URL = defaultURL, digest: String = defaultDigest) {
self.binaryPath = binaryPath
self.url = url
self.digest = digest
}

public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.binaryPath =
try container.decodeIfPresent(String.self, forKey: .binaryPath)
?? Self.defaultBinaryPath
if let urlString = try container.decodeIfPresent(String.self, forKey: .url),
let parsed = URL(string: urlString)
{
if let urlString = try container.decodeIfPresent(String.self, forKey: .url) {
guard let parsed = URL(string: urlString) else {
throw DecodingError.dataCorruptedError(
forKey: .url,
in: container,
debugDescription: "invalid kernel URL '\(urlString)'")
}
self.url = parsed
} else {
self.url = Self.defaultURL
}
if let digest = try container.decodeIfPresent(String.self, forKey: .digest) {
self.digest = digest
} else if self.url.absoluteString == Self.defaultURL.absoluteString {
self.digest = Self.defaultDigest
} else {
throw DecodingError.dataCorruptedError(
forKey: .digest,
in: container,
debugDescription: "kernel.digest is required when kernel.url is not the default URL")
}
}

// JSONEncoder special-cases URL to encode as absoluteString, but third-party
Expand All@@ -209,6 +224,7 @@ final public class KernelConfig: Codable, Sendable {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(binaryPath, forKey: .binaryPath)
try container.encode(url.absoluteString, forKey: .url)
try container.encode(digest, forKey: .digest)
}
}

Expand Down
14 changes: 11 additions & 3 deletions Sources/Services/ContainerAPIService/Client/ClientKernel.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,15 +42,23 @@ extension ClientKernel {
try await client.send(message)
}

public static func installKernelFromTar(tarFile: String, kernelFilePath: String, platform: SystemPlatform, progressUpdate: ProgressUpdateHandler? = nil, force: Bool)
async throws
{
public static func installKernelFromTar(
tarFile: String,
kernelFilePath: String,
platform: SystemPlatform,
progressUpdate: ProgressUpdateHandler? = nil,
expectedDigest: String? = nil,
force: Bool
) async throws {
let client = newClient()
let message = XPCMessage(route: .installKernel)

message.set(key: .kernelTarURL, value: tarFile)
message.set(key: .kernelFilePath, value: kernelFilePath)
message.set(key: .kernelForce, value: force)
if let expectedDigest {
message.set(key: .kernelDigest, value: expectedDigest)
}

let platformData = try JSONEncoder().encode(platform)
message.set(key: .systemPlatform, value: platformData)
Expand Down
1 change: 1 addition & 0 deletions Sources/Services/ContainerAPIService/Client/XPC+.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -112,6 +112,7 @@ public enum XPCKeys: String {
case kernelFilePath
case systemPlatform
case kernelForce
case kernelDigest

/// Init image reference
case initImage
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,7 @@ public struct KernelHarness: Sendable {
let kernelFilePath = try message.kernelFilePath()
let platform = try message.platform()
let force = try message.kernelForce()
let expectedDigest = message.kernelDigest()

guard let kernelTarUrl = try message.kernelTarURL() else {
// We have been given a path to a kernel binary on disk
Expand All@@ -47,7 +48,12 @@ public struct KernelHarness: Sendable {

let progressUpdateService = ProgressUpdateService(message: message)
try await self.service.installKernelFrom(
tar: kernelTarUrl, kernelFilePath: kernelFilePath, platform: platform, progressUpdate: progressUpdateService?.handler, force: force)
tar: kernelTarUrl,
kernelFilePath: kernelFilePath,
platform: platform,
progressUpdate: progressUpdateService?.handler,
expectedDigest: expectedDigest,
force: force)
return message.reply()
}

Expand DownExpand Up@@ -85,13 +91,17 @@ extension XPCMessage {
guard let kernelTarURLString = self.string(key: .kernelTarURL) else {
return nil
}
guard let k = URL(string: kernelTarURLString) else {
throw ContainerizationError(.invalidArgument, message: "cannot parse URL from \(kernelTarURLString)")
if let k = URL(string: kernelTarURLString), k.scheme != nil {
return k
}
return k
return URL(fileURLWithPath: kernelTarURLString)
}

fileprivate func kernelForce() throws -> Bool {
self.bool(key: .kernelForce)
}

fileprivate func kernelDigest() -> String? {
self.string(key: .kernelDigest)
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Package.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -227,6 +227,7 @@ let package = Package(
name: "ContainerAPIServiceTests",
dependencies: [
.product(name: "Containerization", package: "containerization"),
"ContainerAPIService",
"ContainerResource",
"ContainerRuntimeLinuxClient",
"ContainerRuntimeClient",
Expand Down
55 changes: 46 additions & 9 deletions Sources/ContainerCommands/System/Kernel/KernelSet.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,18 +47,25 @@ extension Application {
@Option(name: .customLong("tar"), help: "Filesystem path or remote URL to a tar archive containing a kernel file")
var tarPath: String? = nil

@Option(name: .long, help: "Expected digest for the tar archive, for example sha256:<hex>. Required when --tar is a remote URL.")
var digest: String? = nil

@OptionGroup
public var logOptions: Flags.Logging

public init() {}

public func run() async throws {
let containerSystemConfig: ContainerSystemConfig = try await Application.loadContainerSystemConfig()
if recommended {
let containerSystemConfig: ContainerSystemConfig = try await Application.loadContainerSystemConfig()
let url = containerSystemConfig.kernel.url
let path: String = containerSystemConfig.kernel.binaryPath
log.info("Installing the recommended kernel from \(url)...")
try await Self.downloadAndInstallWithProgressBar(tarRemoteURL: url, kernelFilePath: path, force: force)
try await Self.downloadAndInstallWithProgressBar(
tarRemoteURL: url,
kernelFilePath: path,
expectedDigest: containerSystemConfig.kernel.digest,
force: force)
return
}
guard tarPath != nil else {
Expand All@@ -68,6 +75,9 @@ extension Application {
}

private func setKernelFromBinary() async throws {
guard digest == nil else {
throw ArgumentParser.ValidationError("'--digest' can only be used with '--tar'")
}
guard let binaryPath else {
throw ArgumentParser.ValidationError("missing argument '--binary'")
}
Expand All@@ -84,16 +94,32 @@ extension Application {
throw ArgumentParser.ValidationError("missing argument '--tar")
}
let platform = try getSystemPlatform()
let remoteURL = URL(string: tarPath)
let remoteScheme = remoteURL?.scheme?.lowercased()
let isHTTPURL = remoteScheme == "http" || remoteScheme == "https"
let localTarPath = URL(fileURLWithPath: tarPath, relativeTo: .currentDirectory()).path
let fm = FileManager.default
if fm.fileExists(atPath: localTarPath) {
try await ClientKernel.installKernelFromTar(tarFile: localTarPath, kernelFilePath: binaryPath, platform: platform, force: force)
if !isHTTPURL && fm.fileExists(atPath: localTarPath) {
try await ClientKernel.installKernelFromTar(
tarFile: localTarPath,
kernelFilePath: binaryPath,
platform: platform,
expectedDigest: digest,
force: force)
return
}
guard let remoteURL = URL(string: tarPath) else {
guard let remoteURL else {
throw ContainerizationError(.invalidArgument, message: "invalid remote URL '\(tarPath)' for argument '--tar'. Missing protocol?")
}
try await Self.downloadAndInstallWithProgressBar(tarRemoteURL: remoteURL, kernelFilePath: binaryPath, platform: platform, force: force)
guard let digest else {
throw ArgumentParser.ValidationError("'--digest' is required when '--tar' is a remote URL")
}
try await Self.downloadAndInstallWithProgressBar(
tarRemoteURL: remoteURL,
kernelFilePath: binaryPath,
platform: platform,
expectedDigest: digest,
force: force)
}

private func getSystemPlatform() throws -> SystemPlatform {
Expand All@@ -107,18 +133,29 @@ extension Application {
}
}

static func downloadAndInstallWithProgressBar(tarRemoteURL: URL, kernelFilePath: String, platform: SystemPlatform = .current, force: Bool) async throws {
static func downloadAndInstallWithProgressBar(
tarRemoteURL: URL,
kernelFilePath: String,
platform: SystemPlatform = .current,
expectedDigest: String,
force: Bool
) async throws {
let progressConfig = try ProgressConfig(
showTasks: true,
totalTasks: 2
totalTasks: 3
)
let progress = ProgressBar(config: progressConfig)
defer {
progress.finish()
}
progress.start()
try await ClientKernel.installKernelFromTar(
tarFile: tarRemoteURL.absoluteString, kernelFilePath: kernelFilePath, platform: platform, progressUpdate: progress.handler, force: force)
tarFile: tarRemoteURL.absoluteString,
kernelFilePath: kernelFilePath,
platform: platform,
progressUpdate: progress.handler,
expectedDigest: expectedDigest,
force: force)
progress.finish()
}

Expand Down
13 changes: 10 additions & 3 deletions Sources/ContainerCommands/System/SystemStart.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -157,7 +157,10 @@ extension Application {
guard await !kernelExists() else {
return
}
try await installDefaultKernel(kernelURL: containerSystemConfig.kernel.url, kernelBinaryPath: containerSystemConfig.kernel.binaryPath)
try await installDefaultKernel(
kernelURL: containerSystemConfig.kernel.url,
kernelBinaryPath: containerSystemConfig.kernel.binaryPath,
kernelDigest: containerSystemConfig.kernel.digest)
}

private func installInitialFilesystem(initImage: String) async throws {
Expand All@@ -171,7 +174,7 @@ extension Application {
}
}

private func installDefaultKernel(kernelURL: URL, kernelBinaryPath: String) async throws {
private func installDefaultKernel(kernelURL: URL, kernelBinaryPath: String, kernelDigest: String) async throws {
var shouldInstallKernel = false
if kernelInstall == nil {
print("No default kernel configured.")
Expand All@@ -191,7 +194,11 @@ extension Application {
return
}
log.info("Installing kernel...")
try await KernelSet.downloadAndInstallWithProgressBar(tarRemoteURL: kernelURL, kernelFilePath: kernelBinaryPath, force: true)
try await KernelSet.downloadAndInstallWithProgressBar(
tarRemoteURL: kernelURL,
kernelFilePath: kernelBinaryPath,
expectedDigest: kernelDigest,
force: true)
}

private func initImageExists(containerSystemConfig: ContainerSystemConfig) async -> Bool {
Expand Down
30 changes: 23 additions & 7 deletions Sources/ContainerPersistence/ContainerSystemConfig.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -168,35 +168,50 @@ final public class KernelConfig: Codable, Sendable {
public static let defaultBinaryPath = "opt/kata/share/kata-containers/vmlinux-6.18.15-186"
public static let defaultURL: URL =
URL(string: "https://github.com/kata-containers/kata-containers/releases/download/3.28.0/kata-static-3.28.0-arm64.tar.zst")!
public static let defaultDigest = "sha256:f63d54507d1f18635d94475077e4c2330de4d8e05cedf25f7c38f063b0e66a91"

private enum CodingKeys: String, CodingKey {
case binaryPath
case url
case digest
}

public let binaryPath: String
public let url: URL
public let digest: String

public init(
binaryPath: String = defaultBinaryPath,
url: URL = defaultURL
) {
public init(binaryPath: String = defaultBinaryPath, url: URL = defaultURL, digest: String = defaultDigest) {
self.binaryPath = binaryPath
self.url = url
self.digest = digest
}

public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.binaryPath =
try container.decodeIfPresent(String.self, forKey: .binaryPath)
?? Self.defaultBinaryPath
if let urlString = try container.decodeIfPresent(String.self, forKey: .url),
let parsed = URL(string: urlString)
{
if let urlString = try container.decodeIfPresent(String.self, forKey: .url) {
guard let parsed = URL(string: urlString) else {
throw DecodingError.dataCorruptedError(
forKey: .url,
in: container,
debugDescription: "invalid kernel URL '\(urlString)'")
}
self.url = parsed
} else {
self.url = Self.defaultURL
}
if let digest = try container.decodeIfPresent(String.self, forKey: .digest) {
self.digest = digest
} else if self.url.absoluteString == Self.defaultURL.absoluteString {
self.digest = Self.defaultDigest
} else {
throw DecodingError.dataCorruptedError(
forKey: .digest,
in: container,
debugDescription: "kernel.digest is required when kernel.url is not the default URL")
}
}

// JSONEncoder special-cases URL to encode as absoluteString, but third-party
Expand All@@ -209,6 +224,7 @@ final public class KernelConfig: Codable, Sendable {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(binaryPath, forKey: .binaryPath)
try container.encode(url.absoluteString, forKey: .url)
try container.encode(digest, forKey: .digest)
}
}

Expand Down
14 changes: 11 additions & 3 deletions Sources/Services/ContainerAPIService/Client/ClientKernel.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,15 +42,23 @@ extension ClientKernel {
try await client.send(message)
}

public static func installKernelFromTar(tarFile: String, kernelFilePath: String, platform: SystemPlatform, progressUpdate: ProgressUpdateHandler? = nil, force: Bool)
async throws
{
public static func installKernelFromTar(
tarFile: String,
kernelFilePath: String,
platform: SystemPlatform,
progressUpdate: ProgressUpdateHandler? = nil,
expectedDigest: String? = nil,
force: Bool
) async throws {
let client = newClient()
let message = XPCMessage(route: .installKernel)

message.set(key: .kernelTarURL, value: tarFile)
message.set(key: .kernelFilePath, value: kernelFilePath)
message.set(key: .kernelForce, value: force)
if let expectedDigest {
message.set(key: .kernelDigest, value: expectedDigest)
}

let platformData = try JSONEncoder().encode(platform)
message.set(key: .systemPlatform, value: platformData)
Expand Down
1 change: 1 addition & 0 deletions Sources/Services/ContainerAPIService/Client/XPC+.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -112,6 +112,7 @@ public enum XPCKeys: String {
case kernelFilePath
case systemPlatform
case kernelForce
case kernelDigest

/// Init image reference
case initImage
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,7 @@ public struct KernelHarness: Sendable {
let kernelFilePath = try message.kernelFilePath()
let platform = try message.platform()
let force = try message.kernelForce()
let expectedDigest = message.kernelDigest()

guard let kernelTarUrl = try message.kernelTarURL() else {
// We have been given a path to a kernel binary on disk
Expand All@@ -47,7 +48,12 @@ public struct KernelHarness: Sendable {

let progressUpdateService = ProgressUpdateService(message: message)
try await self.service.installKernelFrom(
tar: kernelTarUrl, kernelFilePath: kernelFilePath, platform: platform, progressUpdate: progressUpdateService?.handler, force: force)
tar: kernelTarUrl,
kernelFilePath: kernelFilePath,
platform: platform,
progressUpdate: progressUpdateService?.handler,
expectedDigest: expectedDigest,
force: force)
return message.reply()
}

Expand DownExpand Up@@ -85,13 +91,17 @@ extension XPCMessage {
guard let kernelTarURLString = self.string(key: .kernelTarURL) else {
return nil
}
guard let k = URL(string: kernelTarURLString) else {
throw ContainerizationError(.invalidArgument, message: "cannot parse URL from \(kernelTarURLString)")
if let k = URL(string: kernelTarURLString), k.scheme != nil {
return k
}
return k
return URL(fileURLWithPath: kernelTarURLString)
}

fileprivate func kernelForce() throws -> Bool {
self.bool(key: .kernelForce)
}

fileprivate func kernelDigest() -> String? {
self.string(key: .kernelDigest)
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Package.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -227,6 +227,7 @@ let package = Package(
name: "ContainerAPIServiceTests",
dependencies: [
.product(name: "Containerization", package: "containerization"),
"ContainerAPIService",
"ContainerResource",
"ContainerRuntimeLinuxClient",
"ContainerRuntimeClient",
Expand Down
55 changes: 46 additions & 9 deletions Sources/ContainerCommands/System/Kernel/KernelSet.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,18 +47,25 @@ extension Application {
@Option(name: .customLong("tar"), help: "Filesystem path or remote URL to a tar archive containing a kernel file")
var tarPath: String? = nil

@Option(name: .long, help: "Expected digest for the tar archive, for example sha256:<hex>. Required when --tar is a remote URL.")
var digest: String? = nil

@OptionGroup
public var logOptions: Flags.Logging

public init() {}

public func run() async throws {
let containerSystemConfig: ContainerSystemConfig = try await Application.loadContainerSystemConfig()
if recommended {
let containerSystemConfig: ContainerSystemConfig = try await Application.loadContainerSystemConfig()
let url = containerSystemConfig.kernel.url
let path: String = containerSystemConfig.kernel.binaryPath
log.info("Installing the recommended kernel from \(url)...")
try await Self.downloadAndInstallWithProgressBar(tarRemoteURL: url, kernelFilePath: path, force: force)
try await Self.downloadAndInstallWithProgressBar(
tarRemoteURL: url,
kernelFilePath: path,
expectedDigest: containerSystemConfig.kernel.digest,
force: force)
return
}
guard tarPath != nil else {
Expand All@@ -68,6 +75,9 @@ extension Application {
}

private func setKernelFromBinary() async throws {
guard digest == nil else {
throw ArgumentParser.ValidationError("'--digest' can only be used with '--tar'")
}
guard let binaryPath else {
throw ArgumentParser.ValidationError("missing argument '--binary'")
}
Expand All@@ -84,16 +94,32 @@ extension Application {
throw ArgumentParser.ValidationError("missing argument '--tar")
}
let platform = try getSystemPlatform()
let remoteURL = URL(string: tarPath)
let remoteScheme = remoteURL?.scheme?.lowercased()
let isHTTPURL = remoteScheme == "http" || remoteScheme == "https"
let localTarPath = URL(fileURLWithPath: tarPath, relativeTo: .currentDirectory()).path
let fm = FileManager.default
if fm.fileExists(atPath: localTarPath) {
try await ClientKernel.installKernelFromTar(tarFile: localTarPath, kernelFilePath: binaryPath, platform: platform, force: force)
if !isHTTPURL && fm.fileExists(atPath: localTarPath) {
try await ClientKernel.installKernelFromTar(
tarFile: localTarPath,
kernelFilePath: binaryPath,
platform: platform,
expectedDigest: digest,
force: force)
return
}
guard let remoteURL = URL(string: tarPath) else {
guard let remoteURL else {
throw ContainerizationError(.invalidArgument, message: "invalid remote URL '\(tarPath)' for argument '--tar'. Missing protocol?")
}
try await Self.downloadAndInstallWithProgressBar(tarRemoteURL: remoteURL, kernelFilePath: binaryPath, platform: platform, force: force)
guard let digest else {
throw ArgumentParser.ValidationError("'--digest' is required when '--tar' is a remote URL")
}
try await Self.downloadAndInstallWithProgressBar(
tarRemoteURL: remoteURL,
kernelFilePath: binaryPath,
platform: platform,
expectedDigest: digest,
force: force)
}

private func getSystemPlatform() throws -> SystemPlatform {
Expand All@@ -107,18 +133,29 @@ extension Application {
}
}

static func downloadAndInstallWithProgressBar(tarRemoteURL: URL, kernelFilePath: String, platform: SystemPlatform = .current, force: Bool) async throws {
static func downloadAndInstallWithProgressBar(
tarRemoteURL: URL,
kernelFilePath: String,
platform: SystemPlatform = .current,
expectedDigest: String,
force: Bool
) async throws {
let progressConfig = try ProgressConfig(
showTasks: true,
totalTasks: 2
totalTasks: 3
)
let progress = ProgressBar(config: progressConfig)
defer {
progress.finish()
}
progress.start()
try await ClientKernel.installKernelFromTar(
tarFile: tarRemoteURL.absoluteString, kernelFilePath: kernelFilePath, platform: platform, progressUpdate: progress.handler, force: force)
tarFile: tarRemoteURL.absoluteString,
kernelFilePath: kernelFilePath,
platform: platform,
progressUpdate: progress.handler,
expectedDigest: expectedDigest,
force: force)
progress.finish()
}

Expand Down
13 changes: 10 additions & 3 deletions Sources/ContainerCommands/System/SystemStart.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -157,7 +157,10 @@ extension Application {
guard await !kernelExists() else {
return
}
try await installDefaultKernel(kernelURL: containerSystemConfig.kernel.url, kernelBinaryPath: containerSystemConfig.kernel.binaryPath)
try await installDefaultKernel(
kernelURL: containerSystemConfig.kernel.url,
kernelBinaryPath: containerSystemConfig.kernel.binaryPath,
kernelDigest: containerSystemConfig.kernel.digest)
}

private func installInitialFilesystem(initImage: String) async throws {
Expand All@@ -171,7 +174,7 @@ extension Application {
}
}

private func installDefaultKernel(kernelURL: URL, kernelBinaryPath: String) async throws {
private func installDefaultKernel(kernelURL: URL, kernelBinaryPath: String, kernelDigest: String) async throws {
var shouldInstallKernel = false
if kernelInstall == nil {
print("No default kernel configured.")
Expand All@@ -191,7 +194,11 @@ extension Application {
return
}
log.info("Installing kernel...")
try await KernelSet.downloadAndInstallWithProgressBar(tarRemoteURL: kernelURL, kernelFilePath: kernelBinaryPath, force: true)
try await KernelSet.downloadAndInstallWithProgressBar(
tarRemoteURL: kernelURL,
kernelFilePath: kernelBinaryPath,
expectedDigest: kernelDigest,
force: true)
}

private func initImageExists(containerSystemConfig: ContainerSystemConfig) async -> Bool {
Expand Down
30 changes: 23 additions & 7 deletions Sources/ContainerPersistence/ContainerSystemConfig.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -168,35 +168,50 @@ final public class KernelConfig: Codable, Sendable {
public static let defaultBinaryPath = "opt/kata/share/kata-containers/vmlinux-6.18.15-186"
public static let defaultURL: URL =
URL(string: "https://github.com/kata-containers/kata-containers/releases/download/3.28.0/kata-static-3.28.0-arm64.tar.zst")!
public static let defaultDigest = "sha256:f63d54507d1f18635d94475077e4c2330de4d8e05cedf25f7c38f063b0e66a91"

private enum CodingKeys: String, CodingKey {
case binaryPath
case url
case digest
}

public let binaryPath: String
public let url: URL
public let digest: String

public init(
binaryPath: String = defaultBinaryPath,
url: URL = defaultURL
) {
public init(binaryPath: String = defaultBinaryPath, url: URL = defaultURL, digest: String = defaultDigest) {
self.binaryPath = binaryPath
self.url = url
self.digest = digest
}

public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.binaryPath =
try container.decodeIfPresent(String.self, forKey: .binaryPath)
?? Self.defaultBinaryPath
if let urlString = try container.decodeIfPresent(String.self, forKey: .url),
let parsed = URL(string: urlString)
{
if let urlString = try container.decodeIfPresent(String.self, forKey: .url) {
guard let parsed = URL(string: urlString) else {
throw DecodingError.dataCorruptedError(
forKey: .url,
in: container,
debugDescription: "invalid kernel URL '\(urlString)'")
}
self.url = parsed
} else {
self.url = Self.defaultURL
}
if let digest = try container.decodeIfPresent(String.self, forKey: .digest) {
self.digest = digest
} else if self.url.absoluteString == Self.defaultURL.absoluteString {
self.digest = Self.defaultDigest
} else {
throw DecodingError.dataCorruptedError(
forKey: .digest,
in: container,
debugDescription: "kernel.digest is required when kernel.url is not the default URL")
}
}

// JSONEncoder special-cases URL to encode as absoluteString, but third-party
Expand All@@ -209,6 +224,7 @@ final public class KernelConfig: Codable, Sendable {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(binaryPath, forKey: .binaryPath)
try container.encode(url.absoluteString, forKey: .url)
try container.encode(digest, forKey: .digest)
}
}

Expand Down
14 changes: 11 additions & 3 deletions Sources/Services/ContainerAPIService/Client/ClientKernel.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,15 +42,23 @@ extension ClientKernel {
try await client.send(message)
}

public static func installKernelFromTar(tarFile: String, kernelFilePath: String, platform: SystemPlatform, progressUpdate: ProgressUpdateHandler? = nil, force: Bool)
async throws
{
public static func installKernelFromTar(
tarFile: String,
kernelFilePath: String,
platform: SystemPlatform,
progressUpdate: ProgressUpdateHandler? = nil,
expectedDigest: String? = nil,
force: Bool
) async throws {
let client = newClient()
let message = XPCMessage(route: .installKernel)

message.set(key: .kernelTarURL, value: tarFile)
message.set(key: .kernelFilePath, value: kernelFilePath)
message.set(key: .kernelForce, value: force)
if let expectedDigest {
message.set(key: .kernelDigest, value: expectedDigest)
}

let platformData = try JSONEncoder().encode(platform)
message.set(key: .systemPlatform, value: platformData)
Expand Down
1 change: 1 addition & 0 deletions Sources/Services/ContainerAPIService/Client/XPC+.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -112,6 +112,7 @@ public enum XPCKeys: String {
case kernelFilePath
case systemPlatform
case kernelForce
case kernelDigest

/// Init image reference
case initImage
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,7 @@ public struct KernelHarness: Sendable {
let kernelFilePath = try message.kernelFilePath()
let platform = try message.platform()
let force = try message.kernelForce()
let expectedDigest = message.kernelDigest()

guard let kernelTarUrl = try message.kernelTarURL() else {
// We have been given a path to a kernel binary on disk
Expand All@@ -47,7 +48,12 @@ public struct KernelHarness: Sendable {

let progressUpdateService = ProgressUpdateService(message: message)
try await self.service.installKernelFrom(
tar: kernelTarUrl, kernelFilePath: kernelFilePath, platform: platform, progressUpdate: progressUpdateService?.handler, force: force)
tar: kernelTarUrl,
kernelFilePath: kernelFilePath,
platform: platform,
progressUpdate: progressUpdateService?.handler,
expectedDigest: expectedDigest,
force: force)
return message.reply()
}

Expand DownExpand Up@@ -85,13 +91,17 @@ extension XPCMessage {
guard let kernelTarURLString = self.string(key: .kernelTarURL) else {
return nil
}
guard let k = URL(string: kernelTarURLString) else {
throw ContainerizationError(.invalidArgument, message: "cannot parse URL from \(kernelTarURLString)")
if let k = URL(string: kernelTarURLString), k.scheme != nil {
return k
}
return k
return URL(fileURLWithPath: kernelTarURLString)
}

fileprivate func kernelForce() throws -> Bool {
self.bool(key: .kernelForce)
}

fileprivate func kernelDigest() -> String? {
self.string(key: .kernelDigest)
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Package.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -227,6 +227,7 @@ let package = Package(
name: "ContainerAPIServiceTests",
dependencies: [
.product(name: "Containerization", package: "containerization"),
"ContainerAPIService",
"ContainerResource",
"ContainerRuntimeLinuxClient",
"ContainerRuntimeClient",
Expand Down
55 changes: 46 additions & 9 deletions Sources/ContainerCommands/System/Kernel/KernelSet.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,18 +47,25 @@ extension Application {
@Option(name: .customLong("tar"), help: "Filesystem path or remote URL to a tar archive containing a kernel file")
var tarPath: String? = nil

@Option(name: .long, help: "Expected digest for the tar archive, for example sha256:<hex>. Required when --tar is a remote URL.")
var digest: String? = nil

@OptionGroup
public var logOptions: Flags.Logging

public init() {}

public func run() async throws {
let containerSystemConfig: ContainerSystemConfig = try await Application.loadContainerSystemConfig()
if recommended {
let containerSystemConfig: ContainerSystemConfig = try await Application.loadContainerSystemConfig()
let url = containerSystemConfig.kernel.url
let path: String = containerSystemConfig.kernel.binaryPath
log.info("Installing the recommended kernel from \(url)...")
try await Self.downloadAndInstallWithProgressBar(tarRemoteURL: url, kernelFilePath: path, force: force)
try await Self.downloadAndInstallWithProgressBar(
tarRemoteURL: url,
kernelFilePath: path,
expectedDigest: containerSystemConfig.kernel.digest,
force: force)
return
}
guard tarPath != nil else {
Expand All@@ -68,6 +75,9 @@ extension Application {
}

private func setKernelFromBinary() async throws {
guard digest == nil else {
throw ArgumentParser.ValidationError("'--digest' can only be used with '--tar'")
}
guard let binaryPath else {
throw ArgumentParser.ValidationError("missing argument '--binary'")
}
Expand All@@ -84,16 +94,32 @@ extension Application {
throw ArgumentParser.ValidationError("missing argument '--tar")
}
let platform = try getSystemPlatform()
let remoteURL = URL(string: tarPath)
let remoteScheme = remoteURL?.scheme?.lowercased()
let isHTTPURL = remoteScheme == "http" || remoteScheme == "https"
let localTarPath = URL(fileURLWithPath: tarPath, relativeTo: .currentDirectory()).path
let fm = FileManager.default
if fm.fileExists(atPath: localTarPath) {
try await ClientKernel.installKernelFromTar(tarFile: localTarPath, kernelFilePath: binaryPath, platform: platform, force: force)
if !isHTTPURL && fm.fileExists(atPath: localTarPath) {
try await ClientKernel.installKernelFromTar(
tarFile: localTarPath,
kernelFilePath: binaryPath,
platform: platform,
expectedDigest: digest,
force: force)
return
}
guard let remoteURL = URL(string: tarPath) else {
guard let remoteURL else {
throw ContainerizationError(.invalidArgument, message: "invalid remote URL '\(tarPath)' for argument '--tar'. Missing protocol?")
}
try await Self.downloadAndInstallWithProgressBar(tarRemoteURL: remoteURL, kernelFilePath: binaryPath, platform: platform, force: force)
guard let digest else {
throw ArgumentParser.ValidationError("'--digest' is required when '--tar' is a remote URL")
}
try await Self.downloadAndInstallWithProgressBar(
tarRemoteURL: remoteURL,
kernelFilePath: binaryPath,
platform: platform,
expectedDigest: digest,
force: force)
}

private func getSystemPlatform() throws -> SystemPlatform {
Expand All@@ -107,18 +133,29 @@ extension Application {
}
}

static func downloadAndInstallWithProgressBar(tarRemoteURL: URL, kernelFilePath: String, platform: SystemPlatform = .current, force: Bool) async throws {
static func downloadAndInstallWithProgressBar(
tarRemoteURL: URL,
kernelFilePath: String,
platform: SystemPlatform = .current,
expectedDigest: String,
force: Bool
) async throws {
let progressConfig = try ProgressConfig(
showTasks: true,
totalTasks: 2
totalTasks: 3
)
let progress = ProgressBar(config: progressConfig)
defer {
progress.finish()
}
progress.start()
try await ClientKernel.installKernelFromTar(
tarFile: tarRemoteURL.absoluteString, kernelFilePath: kernelFilePath, platform: platform, progressUpdate: progress.handler, force: force)
tarFile: tarRemoteURL.absoluteString,
kernelFilePath: kernelFilePath,
platform: platform,
progressUpdate: progress.handler,
expectedDigest: expectedDigest,
force: force)
progress.finish()
}

Expand Down
13 changes: 10 additions & 3 deletions Sources/ContainerCommands/System/SystemStart.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -157,7 +157,10 @@ extension Application {
guard await !kernelExists() else {
return
}
try await installDefaultKernel(kernelURL: containerSystemConfig.kernel.url, kernelBinaryPath: containerSystemConfig.kernel.binaryPath)
try await installDefaultKernel(
kernelURL: containerSystemConfig.kernel.url,
kernelBinaryPath: containerSystemConfig.kernel.binaryPath,
kernelDigest: containerSystemConfig.kernel.digest)
}

private func installInitialFilesystem(initImage: String) async throws {
Expand All@@ -171,7 +174,7 @@ extension Application {
}
}

private func installDefaultKernel(kernelURL: URL, kernelBinaryPath: String) async throws {
private func installDefaultKernel(kernelURL: URL, kernelBinaryPath: String, kernelDigest: String) async throws {
var shouldInstallKernel = false
if kernelInstall == nil {
print("No default kernel configured.")
Expand All@@ -191,7 +194,11 @@ extension Application {
return
}
log.info("Installing kernel...")
try await KernelSet.downloadAndInstallWithProgressBar(tarRemoteURL: kernelURL, kernelFilePath: kernelBinaryPath, force: true)
try await KernelSet.downloadAndInstallWithProgressBar(
tarRemoteURL: kernelURL,
kernelFilePath: kernelBinaryPath,
expectedDigest: kernelDigest,
force: true)
}

private func initImageExists(containerSystemConfig: ContainerSystemConfig) async -> Bool {
Expand Down
30 changes: 23 additions & 7 deletions Sources/ContainerPersistence/ContainerSystemConfig.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -168,35 +168,50 @@ final public class KernelConfig: Codable, Sendable {
public static let defaultBinaryPath = "opt/kata/share/kata-containers/vmlinux-6.18.15-186"
public static let defaultURL: URL =
URL(string: "https://github.com/kata-containers/kata-containers/releases/download/3.28.0/kata-static-3.28.0-arm64.tar.zst")!
public static let defaultDigest = "sha256:f63d54507d1f18635d94475077e4c2330de4d8e05cedf25f7c38f063b0e66a91"

private enum CodingKeys: String, CodingKey {
case binaryPath
case url
case digest
}

public let binaryPath: String
public let url: URL
public let digest: String

public init(
binaryPath: String = defaultBinaryPath,
url: URL = defaultURL
) {
public init(binaryPath: String = defaultBinaryPath, url: URL = defaultURL, digest: String = defaultDigest) {
self.binaryPath = binaryPath
self.url = url
self.digest = digest
}

public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.binaryPath =
try container.decodeIfPresent(String.self, forKey: .binaryPath)
?? Self.defaultBinaryPath
if let urlString = try container.decodeIfPresent(String.self, forKey: .url),
let parsed = URL(string: urlString)
{
if let urlString = try container.decodeIfPresent(String.self, forKey: .url) {
guard let parsed = URL(string: urlString) else {
throw DecodingError.dataCorruptedError(
forKey: .url,
in: container,
debugDescription: "invalid kernel URL '\(urlString)'")
}
self.url = parsed
} else {
self.url = Self.defaultURL
}
if let digest = try container.decodeIfPresent(String.self, forKey: .digest) {
self.digest = digest
} else if self.url.absoluteString == Self.defaultURL.absoluteString {
self.digest = Self.defaultDigest
} else {
throw DecodingError.dataCorruptedError(
forKey: .digest,
in: container,
debugDescription: "kernel.digest is required when kernel.url is not the default URL")
}
}

// JSONEncoder special-cases URL to encode as absoluteString, but third-party
Expand All@@ -209,6 +224,7 @@ final public class KernelConfig: Codable, Sendable {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(binaryPath, forKey: .binaryPath)
try container.encode(url.absoluteString, forKey: .url)
try container.encode(digest, forKey: .digest)
}
}

Expand Down
14 changes: 11 additions & 3 deletions Sources/Services/ContainerAPIService/Client/ClientKernel.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,15 +42,23 @@ extension ClientKernel {
try await client.send(message)
}

public static func installKernelFromTar(tarFile: String, kernelFilePath: String, platform: SystemPlatform, progressUpdate: ProgressUpdateHandler? = nil, force: Bool)
async throws
{
public static func installKernelFromTar(
tarFile: String,
kernelFilePath: String,
platform: SystemPlatform,
progressUpdate: ProgressUpdateHandler? = nil,
expectedDigest: String? = nil,
force: Bool
) async throws {
let client = newClient()
let message = XPCMessage(route: .installKernel)

message.set(key: .kernelTarURL, value: tarFile)
message.set(key: .kernelFilePath, value: kernelFilePath)
message.set(key: .kernelForce, value: force)
if let expectedDigest {
message.set(key: .kernelDigest, value: expectedDigest)
}

let platformData = try JSONEncoder().encode(platform)
message.set(key: .systemPlatform, value: platformData)
Expand Down
1 change: 1 addition & 0 deletions Sources/Services/ContainerAPIService/Client/XPC+.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -112,6 +112,7 @@ public enum XPCKeys: String {
case kernelFilePath
case systemPlatform
case kernelForce
case kernelDigest

/// Init image reference
case initImage
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,7 @@ public struct KernelHarness: Sendable {
let kernelFilePath = try message.kernelFilePath()
let platform = try message.platform()
let force = try message.kernelForce()
let expectedDigest = message.kernelDigest()

guard let kernelTarUrl = try message.kernelTarURL() else {
// We have been given a path to a kernel binary on disk
Expand All@@ -47,7 +48,12 @@ public struct KernelHarness: Sendable {

let progressUpdateService = ProgressUpdateService(message: message)
try await self.service.installKernelFrom(
tar: kernelTarUrl, kernelFilePath: kernelFilePath, platform: platform, progressUpdate: progressUpdateService?.handler, force: force)
tar: kernelTarUrl,
kernelFilePath: kernelFilePath,
platform: platform,
progressUpdate: progressUpdateService?.handler,
expectedDigest: expectedDigest,
force: force)
return message.reply()
}

Expand DownExpand Up@@ -85,13 +91,17 @@ extension XPCMessage {
guard let kernelTarURLString = self.string(key: .kernelTarURL) else {
return nil
}
guard let k = URL(string: kernelTarURLString) else {
throw ContainerizationError(.invalidArgument, message: "cannot parse URL from \(kernelTarURLString)")
if let k = URL(string: kernelTarURLString), k.scheme != nil {
return k
}
return k
return URL(fileURLWithPath: kernelTarURLString)
}

fileprivate func kernelForce() throws -> Bool {
self.bool(key: .kernelForce)
}

fileprivate func kernelDigest() -> String? {
self.string(key: .kernelDigest)
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Package.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -227,6 +227,7 @@ let package = Package(
name: "ContainerAPIServiceTests",
dependencies: [
.product(name: "Containerization", package: "containerization"),
"ContainerAPIService",
"ContainerResource",
"ContainerRuntimeLinuxClient",
"ContainerRuntimeClient",
Expand Down
55 changes: 46 additions & 9 deletions Sources/ContainerCommands/System/Kernel/KernelSet.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,18 +47,25 @@ extension Application {
@Option(name: .customLong("tar"), help: "Filesystem path or remote URL to a tar archive containing a kernel file")
var tarPath: String? = nil

@Option(name: .long, help: "Expected digest for the tar archive, for example sha256:<hex>. Required when --tar is a remote URL.")
var digest: String? = nil

@OptionGroup
public var logOptions: Flags.Logging

public init() {}

public func run() async throws {
let containerSystemConfig: ContainerSystemConfig = try await Application.loadContainerSystemConfig()
if recommended {
let containerSystemConfig: ContainerSystemConfig = try await Application.loadContainerSystemConfig()
let url = containerSystemConfig.kernel.url
let path: String = containerSystemConfig.kernel.binaryPath
log.info("Installing the recommended kernel from \(url)...")
try await Self.downloadAndInstallWithProgressBar(tarRemoteURL: url, kernelFilePath: path, force: force)
try await Self.downloadAndInstallWithProgressBar(
tarRemoteURL: url,
kernelFilePath: path,
expectedDigest: containerSystemConfig.kernel.digest,
force: force)
return
}
guard tarPath != nil else {
Expand All@@ -68,6 +75,9 @@ extension Application {
}

private func setKernelFromBinary() async throws {
guard digest == nil else {
throw ArgumentParser.ValidationError("'--digest' can only be used with '--tar'")
}
guard let binaryPath else {
throw ArgumentParser.ValidationError("missing argument '--binary'")
}
Expand All@@ -84,16 +94,32 @@ extension Application {
throw ArgumentParser.ValidationError("missing argument '--tar")
}
let platform = try getSystemPlatform()
let remoteURL = URL(string: tarPath)
let remoteScheme = remoteURL?.scheme?.lowercased()
let isHTTPURL = remoteScheme == "http" || remoteScheme == "https"
let localTarPath = URL(fileURLWithPath: tarPath, relativeTo: .currentDirectory()).path
let fm = FileManager.default
if fm.fileExists(atPath: localTarPath) {
try await ClientKernel.installKernelFromTar(tarFile: localTarPath, kernelFilePath: binaryPath, platform: platform, force: force)
if !isHTTPURL && fm.fileExists(atPath: localTarPath) {
try await ClientKernel.installKernelFromTar(
tarFile: localTarPath,
kernelFilePath: binaryPath,
platform: platform,
expectedDigest: digest,
force: force)
return
}
guard let remoteURL = URL(string: tarPath) else {
guard let remoteURL else {
throw ContainerizationError(.invalidArgument, message: "invalid remote URL '\(tarPath)' for argument '--tar'. Missing protocol?")
}
try await Self.downloadAndInstallWithProgressBar(tarRemoteURL: remoteURL, kernelFilePath: binaryPath, platform: platform, force: force)
guard let digest else {
throw ArgumentParser.ValidationError("'--digest' is required when '--tar' is a remote URL")
}
try await Self.downloadAndInstallWithProgressBar(
tarRemoteURL: remoteURL,
kernelFilePath: binaryPath,
platform: platform,
expectedDigest: digest,
force: force)
}

private func getSystemPlatform() throws -> SystemPlatform {
Expand All@@ -107,18 +133,29 @@ extension Application {
}
}

static func downloadAndInstallWithProgressBar(tarRemoteURL: URL, kernelFilePath: String, platform: SystemPlatform = .current, force: Bool) async throws {
static func downloadAndInstallWithProgressBar(
tarRemoteURL: URL,
kernelFilePath: String,
platform: SystemPlatform = .current,
expectedDigest: String,
force: Bool
) async throws {
let progressConfig = try ProgressConfig(
showTasks: true,
totalTasks: 2
totalTasks: 3
)
let progress = ProgressBar(config: progressConfig)
defer {
progress.finish()
}
progress.start()
try await ClientKernel.installKernelFromTar(
tarFile: tarRemoteURL.absoluteString, kernelFilePath: kernelFilePath, platform: platform, progressUpdate: progress.handler, force: force)
tarFile: tarRemoteURL.absoluteString,
kernelFilePath: kernelFilePath,
platform: platform,
progressUpdate: progress.handler,
expectedDigest: expectedDigest,
force: force)
progress.finish()
}

Expand Down
13 changes: 10 additions & 3 deletions Sources/ContainerCommands/System/SystemStart.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -157,7 +157,10 @@ extension Application {
guard await !kernelExists() else {
return
}
try await installDefaultKernel(kernelURL: containerSystemConfig.kernel.url, kernelBinaryPath: containerSystemConfig.kernel.binaryPath)
try await installDefaultKernel(
kernelURL: containerSystemConfig.kernel.url,
kernelBinaryPath: containerSystemConfig.kernel.binaryPath,
kernelDigest: containerSystemConfig.kernel.digest)
}

private func installInitialFilesystem(initImage: String) async throws {
Expand All@@ -171,7 +174,7 @@ extension Application {
}
}

private func installDefaultKernel(kernelURL: URL, kernelBinaryPath: String) async throws {
private func installDefaultKernel(kernelURL: URL, kernelBinaryPath: String, kernelDigest: String) async throws {
var shouldInstallKernel = false
if kernelInstall == nil {
print("No default kernel configured.")
Expand All@@ -191,7 +194,11 @@ extension Application {
return
}
log.info("Installing kernel...")
try await KernelSet.downloadAndInstallWithProgressBar(tarRemoteURL: kernelURL, kernelFilePath: kernelBinaryPath, force: true)
try await KernelSet.downloadAndInstallWithProgressBar(
tarRemoteURL: kernelURL,
kernelFilePath: kernelBinaryPath,
expectedDigest: kernelDigest,
force: true)
}

private func initImageExists(containerSystemConfig: ContainerSystemConfig) async -> Bool {
Expand Down
30 changes: 23 additions & 7 deletions Sources/ContainerPersistence/ContainerSystemConfig.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -168,35 +168,50 @@ final public class KernelConfig: Codable, Sendable {
public static let defaultBinaryPath = "opt/kata/share/kata-containers/vmlinux-6.18.15-186"
public static let defaultURL: URL =
URL(string: "https://github.com/kata-containers/kata-containers/releases/download/3.28.0/kata-static-3.28.0-arm64.tar.zst")!
public static let defaultDigest = "sha256:f63d54507d1f18635d94475077e4c2330de4d8e05cedf25f7c38f063b0e66a91"

private enum CodingKeys: String, CodingKey {
case binaryPath
case url
case digest
}

public let binaryPath: String
public let url: URL
public let digest: String

public init(
binaryPath: String = defaultBinaryPath,
url: URL = defaultURL
) {
public init(binaryPath: String = defaultBinaryPath, url: URL = defaultURL, digest: String = defaultDigest) {
self.binaryPath = binaryPath
self.url = url
self.digest = digest
}

public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.binaryPath =
try container.decodeIfPresent(String.self, forKey: .binaryPath)
?? Self.defaultBinaryPath
if let urlString = try container.decodeIfPresent(String.self, forKey: .url),
let parsed = URL(string: urlString)
{
if let urlString = try container.decodeIfPresent(String.self, forKey: .url) {
guard let parsed = URL(string: urlString) else {
throw DecodingError.dataCorruptedError(
forKey: .url,
in: container,
debugDescription: "invalid kernel URL '\(urlString)'")
}
self.url = parsed
} else {
self.url = Self.defaultURL
}
if let digest = try container.decodeIfPresent(String.self, forKey: .digest) {
self.digest = digest
} else if self.url.absoluteString == Self.defaultURL.absoluteString {
self.digest = Self.defaultDigest
} else {
throw DecodingError.dataCorruptedError(
forKey: .digest,
in: container,
debugDescription: "kernel.digest is required when kernel.url is not the default URL")
}
}

// JSONEncoder special-cases URL to encode as absoluteString, but third-party
Expand All@@ -209,6 +224,7 @@ final public class KernelConfig: Codable, Sendable {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(binaryPath, forKey: .binaryPath)
try container.encode(url.absoluteString, forKey: .url)
try container.encode(digest, forKey: .digest)
}
}

Expand Down
14 changes: 11 additions & 3 deletions Sources/Services/ContainerAPIService/Client/ClientKernel.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,15 +42,23 @@ extension ClientKernel {
try await client.send(message)
}

public static func installKernelFromTar(tarFile: String, kernelFilePath: String, platform: SystemPlatform, progressUpdate: ProgressUpdateHandler? = nil, force: Bool)
async throws
{
public static func installKernelFromTar(
tarFile: String,
kernelFilePath: String,
platform: SystemPlatform,
progressUpdate: ProgressUpdateHandler? = nil,
expectedDigest: String? = nil,
force: Bool
) async throws {
let client = newClient()
let message = XPCMessage(route: .installKernel)

message.set(key: .kernelTarURL, value: tarFile)
message.set(key: .kernelFilePath, value: kernelFilePath)
message.set(key: .kernelForce, value: force)
if let expectedDigest {
message.set(key: .kernelDigest, value: expectedDigest)
}

let platformData = try JSONEncoder().encode(platform)
message.set(key: .systemPlatform, value: platformData)
Expand Down
1 change: 1 addition & 0 deletions Sources/Services/ContainerAPIService/Client/XPC+.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -112,6 +112,7 @@ public enum XPCKeys: String {
case kernelFilePath
case systemPlatform
case kernelForce
case kernelDigest

/// Init image reference
case initImage
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,7 @@ public struct KernelHarness: Sendable {
let kernelFilePath = try message.kernelFilePath()
let platform = try message.platform()
let force = try message.kernelForce()
let expectedDigest = message.kernelDigest()

guard let kernelTarUrl = try message.kernelTarURL() else {
// We have been given a path to a kernel binary on disk
Expand All@@ -47,7 +48,12 @@ public struct KernelHarness: Sendable {

let progressUpdateService = ProgressUpdateService(message: message)
try await self.service.installKernelFrom(
tar: kernelTarUrl, kernelFilePath: kernelFilePath, platform: platform, progressUpdate: progressUpdateService?.handler, force: force)
tar: kernelTarUrl,
kernelFilePath: kernelFilePath,
platform: platform,
progressUpdate: progressUpdateService?.handler,
expectedDigest: expectedDigest,
force: force)
return message.reply()
}

Expand DownExpand Up@@ -85,13 +91,17 @@ extension XPCMessage {
guard let kernelTarURLString = self.string(key: .kernelTarURL) else {
return nil
}
guard let k = URL(string: kernelTarURLString) else {
throw ContainerizationError(.invalidArgument, message: "cannot parse URL from \(kernelTarURLString)")
if let k = URL(string: kernelTarURLString), k.scheme != nil {
return k
}
return k
return URL(fileURLWithPath: kernelTarURLString)
}

fileprivate func kernelForce() throws -> Bool {
self.bool(key: .kernelForce)
}

fileprivate func kernelDigest() -> String? {
self.string(key: .kernelDigest)
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Package.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -227,6 +227,7 @@ let package = Package(
name: "ContainerAPIServiceTests",
dependencies: [
.product(name: "Containerization", package: "containerization"),
"ContainerAPIService",
"ContainerResource",
"ContainerRuntimeLinuxClient",
"ContainerRuntimeClient",
Expand Down
55 changes: 46 additions & 9 deletions Sources/ContainerCommands/System/Kernel/KernelSet.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,18 +47,25 @@ extension Application {
@Option(name: .customLong("tar"), help: "Filesystem path or remote URL to a tar archive containing a kernel file")
var tarPath: String? = nil

@Option(name: .long, help: "Expected digest for the tar archive, for example sha256:<hex>. Required when --tar is a remote URL.")
var digest: String? = nil

@OptionGroup
public var logOptions: Flags.Logging

public init() {}

public func run() async throws {
let containerSystemConfig: ContainerSystemConfig = try await Application.loadContainerSystemConfig()
if recommended {
let containerSystemConfig: ContainerSystemConfig = try await Application.loadContainerSystemConfig()
let url = containerSystemConfig.kernel.url
let path: String = containerSystemConfig.kernel.binaryPath
log.info("Installing the recommended kernel from \(url)...")
try await Self.downloadAndInstallWithProgressBar(tarRemoteURL: url, kernelFilePath: path, force: force)
try await Self.downloadAndInstallWithProgressBar(
tarRemoteURL: url,
kernelFilePath: path,
expectedDigest: containerSystemConfig.kernel.digest,
force: force)
return
}
guard tarPath != nil else {
Expand All@@ -68,6 +75,9 @@ extension Application {
}

private func setKernelFromBinary() async throws {
guard digest == nil else {
throw ArgumentParser.ValidationError("'--digest' can only be used with '--tar'")
}
guard let binaryPath else {
throw ArgumentParser.ValidationError("missing argument '--binary'")
}
Expand All@@ -84,16 +94,32 @@ extension Application {
throw ArgumentParser.ValidationError("missing argument '--tar")
}
let platform = try getSystemPlatform()
let remoteURL = URL(string: tarPath)
let remoteScheme = remoteURL?.scheme?.lowercased()
let isHTTPURL = remoteScheme == "http" || remoteScheme == "https"
let localTarPath = URL(fileURLWithPath: tarPath, relativeTo: .currentDirectory()).path
let fm = FileManager.default
if fm.fileExists(atPath: localTarPath) {
try await ClientKernel.installKernelFromTar(tarFile: localTarPath, kernelFilePath: binaryPath, platform: platform, force: force)
if !isHTTPURL && fm.fileExists(atPath: localTarPath) {
try await ClientKernel.installKernelFromTar(
tarFile: localTarPath,
kernelFilePath: binaryPath,
platform: platform,
expectedDigest: digest,
force: force)
return
}
guard let remoteURL = URL(string: tarPath) else {
guard let remoteURL else {
throw ContainerizationError(.invalidArgument, message: "invalid remote URL '\(tarPath)' for argument '--tar'. Missing protocol?")
}
try await Self.downloadAndInstallWithProgressBar(tarRemoteURL: remoteURL, kernelFilePath: binaryPath, platform: platform, force: force)
guard let digest else {
throw ArgumentParser.ValidationError("'--digest' is required when '--tar' is a remote URL")
}
try await Self.downloadAndInstallWithProgressBar(
tarRemoteURL: remoteURL,
kernelFilePath: binaryPath,
platform: platform,
expectedDigest: digest,
force: force)
}

private func getSystemPlatform() throws -> SystemPlatform {
Expand All@@ -107,18 +133,29 @@ extension Application {
}
}

static func downloadAndInstallWithProgressBar(tarRemoteURL: URL, kernelFilePath: String, platform: SystemPlatform = .current, force: Bool) async throws {
static func downloadAndInstallWithProgressBar(
tarRemoteURL: URL,
kernelFilePath: String,
platform: SystemPlatform = .current,
expectedDigest: String,
force: Bool
) async throws {
let progressConfig = try ProgressConfig(
showTasks: true,
totalTasks: 2
totalTasks: 3
)
let progress = ProgressBar(config: progressConfig)
defer {
progress.finish()
}
progress.start()
try await ClientKernel.installKernelFromTar(
tarFile: tarRemoteURL.absoluteString, kernelFilePath: kernelFilePath, platform: platform, progressUpdate: progress.handler, force: force)
tarFile: tarRemoteURL.absoluteString,
kernelFilePath: kernelFilePath,
platform: platform,
progressUpdate: progress.handler,
expectedDigest: expectedDigest,
force: force)
progress.finish()
}

Expand Down
13 changes: 10 additions & 3 deletions Sources/ContainerCommands/System/SystemStart.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -157,7 +157,10 @@ extension Application {
guard await !kernelExists() else {
return
}
try await installDefaultKernel(kernelURL: containerSystemConfig.kernel.url, kernelBinaryPath: containerSystemConfig.kernel.binaryPath)
try await installDefaultKernel(
kernelURL: containerSystemConfig.kernel.url,
kernelBinaryPath: containerSystemConfig.kernel.binaryPath,
kernelDigest: containerSystemConfig.kernel.digest)
}

private func installInitialFilesystem(initImage: String) async throws {
Expand All@@ -171,7 +174,7 @@ extension Application {
}
}

private func installDefaultKernel(kernelURL: URL, kernelBinaryPath: String) async throws {
private func installDefaultKernel(kernelURL: URL, kernelBinaryPath: String, kernelDigest: String) async throws {
var shouldInstallKernel = false
if kernelInstall == nil {
print("No default kernel configured.")
Expand All@@ -191,7 +194,11 @@ extension Application {
return
}
log.info("Installing kernel...")
try await KernelSet.downloadAndInstallWithProgressBar(tarRemoteURL: kernelURL, kernelFilePath: kernelBinaryPath, force: true)
try await KernelSet.downloadAndInstallWithProgressBar(
tarRemoteURL: kernelURL,
kernelFilePath: kernelBinaryPath,
expectedDigest: kernelDigest,
force: true)
}

private func initImageExists(containerSystemConfig: ContainerSystemConfig) async -> Bool {
Expand Down
30 changes: 23 additions & 7 deletions Sources/ContainerPersistence/ContainerSystemConfig.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -168,35 +168,50 @@ final public class KernelConfig: Codable, Sendable {
public static let defaultBinaryPath = "opt/kata/share/kata-containers/vmlinux-6.18.15-186"
public static let defaultURL: URL =
URL(string: "https://github.com/kata-containers/kata-containers/releases/download/3.28.0/kata-static-3.28.0-arm64.tar.zst")!
public static let defaultDigest = "sha256:f63d54507d1f18635d94475077e4c2330de4d8e05cedf25f7c38f063b0e66a91"

private enum CodingKeys: String, CodingKey {
case binaryPath
case url
case digest
}

public let binaryPath: String
public let url: URL
public let digest: String

public init(
binaryPath: String = defaultBinaryPath,
url: URL = defaultURL
) {
public init(binaryPath: String = defaultBinaryPath, url: URL = defaultURL, digest: String = defaultDigest) {
self.binaryPath = binaryPath
self.url = url
self.digest = digest
}

public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.binaryPath =
try container.decodeIfPresent(String.self, forKey: .binaryPath)
?? Self.defaultBinaryPath
if let urlString = try container.decodeIfPresent(String.self, forKey: .url),
let parsed = URL(string: urlString)
{
if let urlString = try container.decodeIfPresent(String.self, forKey: .url) {
guard let parsed = URL(string: urlString) else {
throw DecodingError.dataCorruptedError(
forKey: .url,
in: container,
debugDescription: "invalid kernel URL '\(urlString)'")
}
self.url = parsed
} else {
self.url = Self.defaultURL
}
if let digest = try container.decodeIfPresent(String.self, forKey: .digest) {
self.digest = digest
} else if self.url.absoluteString == Self.defaultURL.absoluteString {
self.digest = Self.defaultDigest
} else {
throw DecodingError.dataCorruptedError(
forKey: .digest,
in: container,
debugDescription: "kernel.digest is required when kernel.url is not the default URL")
}
}

// JSONEncoder special-cases URL to encode as absoluteString, but third-party
Expand All@@ -209,6 +224,7 @@ final public class KernelConfig: Codable, Sendable {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(binaryPath, forKey: .binaryPath)
try container.encode(url.absoluteString, forKey: .url)
try container.encode(digest, forKey: .digest)
}
}

Expand Down
14 changes: 11 additions & 3 deletions Sources/Services/ContainerAPIService/Client/ClientKernel.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,15 +42,23 @@ extension ClientKernel {
try await client.send(message)
}

public static func installKernelFromTar(tarFile: String, kernelFilePath: String, platform: SystemPlatform, progressUpdate: ProgressUpdateHandler? = nil, force: Bool)
async throws
{
public static func installKernelFromTar(
tarFile: String,
kernelFilePath: String,
platform: SystemPlatform,
progressUpdate: ProgressUpdateHandler? = nil,
expectedDigest: String? = nil,
force: Bool
) async throws {
let client = newClient()
let message = XPCMessage(route: .installKernel)

message.set(key: .kernelTarURL, value: tarFile)
message.set(key: .kernelFilePath, value: kernelFilePath)
message.set(key: .kernelForce, value: force)
if let expectedDigest {
message.set(key: .kernelDigest, value: expectedDigest)
}

let platformData = try JSONEncoder().encode(platform)
message.set(key: .systemPlatform, value: platformData)
Expand Down
1 change: 1 addition & 0 deletions Sources/Services/ContainerAPIService/Client/XPC+.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -112,6 +112,7 @@ public enum XPCKeys: String {
case kernelFilePath
case systemPlatform
case kernelForce
case kernelDigest

/// Init image reference
case initImage
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,7 @@ public struct KernelHarness: Sendable {
let kernelFilePath = try message.kernelFilePath()
let platform = try message.platform()
let force = try message.kernelForce()
let expectedDigest = message.kernelDigest()

guard let kernelTarUrl = try message.kernelTarURL() else {
// We have been given a path to a kernel binary on disk
Expand All@@ -47,7 +48,12 @@ public struct KernelHarness: Sendable {

let progressUpdateService = ProgressUpdateService(message: message)
try await self.service.installKernelFrom(
tar: kernelTarUrl, kernelFilePath: kernelFilePath, platform: platform, progressUpdate: progressUpdateService?.handler, force: force)
tar: kernelTarUrl,
kernelFilePath: kernelFilePath,
platform: platform,
progressUpdate: progressUpdateService?.handler,
expectedDigest: expectedDigest,
force: force)
return message.reply()
}

Expand DownExpand Up@@ -85,13 +91,17 @@ extension XPCMessage {
guard let kernelTarURLString = self.string(key: .kernelTarURL) else {
return nil
}
guard let k = URL(string: kernelTarURLString) else {
throw ContainerizationError(.invalidArgument, message: "cannot parse URL from \(kernelTarURLString)")
if let k = URL(string: kernelTarURLString), k.scheme != nil {
return k
}
return k
return URL(fileURLWithPath: kernelTarURLString)
}

fileprivate func kernelForce() throws -> Bool {
self.bool(key: .kernelForce)
}

fileprivate func kernelDigest() -> String? {
self.string(key: .kernelDigest)
}
}
Loading