Skip to content

Commit 076d28e

Browse files
authored
Add and use container-runtime-linux start. (#654)
- Part of #653. - No need for `defaultCommand` since ContainerService startup will write a new launchd plist with the `start` subcommand included. ## Type of Change - [ ] Bug fix - [ ] New feature - [ ] Breaking change - [ ] Documentation update ## Motivation and Context Allows us to add commands to support post-installation, migration, etc. ## Testing - [x] Tested locally - [ ] Added/updated tests - [ ] Added/updated docs
1 parent e460ca9 commit 076d28e

File tree

4 files changed

+149
-115
lines changed

4 files changed

+149
-115
lines changed

Sources/ContainerCommands/System/SystemStart.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ extension Application {
3131

3232
@Option(
3333
name: .shortAndLong,
34-
help: "Application data directory",
34+
help: "Path to the root directory for application data",
3535
transform: { URL(filePath: $0) })
3636
var appRoot = ApplicationRoot.defaultURL
3737

3838
@Option(
3939
name: .long,
40-
help: "Path to the installation root directory",
40+
help: "Path to the root directory for application executables and plugins",
4141
transform: { URL(filePath: $0) })
4242
var installRoot = InstallRoot.defaultURL
4343

@@ -54,7 +54,7 @@ extension Application {
5454

5555
public func run() async throws {
5656
// Without the true path to the binary in the plist, `container-apiserver` won't launch properly.
57-
// TODO: Use plugin loader for API server.
57+
// TODO: Can we use the plugin loader to bootstrap the API server?
5858
let executableUrl = CommandLine.executablePathUrl
5959
.deletingLastPathComponent()
6060
.appendingPathComponent("container-apiserver")
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
//===----------------------------------------------------------------------===//
2+
// Copyright © 2025 Apple Inc. and the container project authors. All rights reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// https://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//===----------------------------------------------------------------------===//
16+
17+
import ArgumentParser
18+
import ContainerClient
19+
import ContainerLog
20+
import ContainerSandboxService
21+
import ContainerXPC
22+
import Foundation
23+
import Logging
24+
import NIO
25+
26+
extension RuntimeLinuxHelper {
27+
struct Start: AsyncParsableCommand {
28+
static let label = "com.apple.container.runtime.container-runtime-linux"
29+
30+
static let configuration = CommandConfiguration(
31+
commandName: "start",
32+
abstract: "Start helper for a Linux container"
33+
)
34+
35+
@Flag(name: .long, help: "Enable debug logging")
36+
var debug = false
37+
38+
@Option(name: .shortAndLong, help: "Sandbox UUID")
39+
var uuid: String
40+
41+
@Option(name: .shortAndLong, help: "Root directory for the sandbox")
42+
var root: String
43+
44+
var machServiceLabel: String {
45+
"\(Self.label).\(uuid)"
46+
}
47+
48+
func run() async throws {
49+
let commandName = Self._commandName
50+
let log = RuntimeLinuxHelper.setupLogger(debug: debug, metadata: ["uuid": "\(uuid)"])
51+
52+
log.info("starting \(commandName)")
53+
defer {
54+
log.info("stopping \(commandName)")
55+
}
56+
57+
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
58+
do {
59+
try adjustLimits()
60+
signal(SIGPIPE, SIG_IGN)
61+
62+
log.info("configuring XPC server")
63+
let interfaceStrategy: any InterfaceStrategy
64+
if #available(macOS 26, *) {
65+
interfaceStrategy = NonisolatedInterfaceStrategy(log: log)
66+
} else {
67+
interfaceStrategy = IsolatedInterfaceStrategy()
68+
}
69+
70+
nonisolated(unsafe) let anonymousConnection = xpc_connection_create(nil, nil)
71+
let server = SandboxService(
72+
root: .init(fileURLWithPath: root),
73+
interfaceStrategy: interfaceStrategy,
74+
eventLoopGroup: eventLoopGroup,
75+
connection: anonymousConnection,
76+
log: log
77+
)
78+
79+
let endpointServer = XPCServer(
80+
identifier: machServiceLabel,
81+
routes: [
82+
SandboxRoutes.createEndpoint.rawValue: server.createEndpoint
83+
],
84+
log: log
85+
)
86+
87+
let mainServer = XPCServer(
88+
connection: anonymousConnection,
89+
routes: [
90+
SandboxRoutes.bootstrap.rawValue: server.bootstrap,
91+
SandboxRoutes.createProcess.rawValue: server.createProcess,
92+
SandboxRoutes.state.rawValue: server.state,
93+
SandboxRoutes.stop.rawValue: server.stop,
94+
SandboxRoutes.kill.rawValue: server.kill,
95+
SandboxRoutes.resize.rawValue: server.resize,
96+
SandboxRoutes.wait.rawValue: server.wait,
97+
SandboxRoutes.start.rawValue: server.startProcess,
98+
SandboxRoutes.dial.rawValue: server.dial,
99+
],
100+
log: log
101+
)
102+
103+
log.info("starting XPC server")
104+
try await withThrowingTaskGroup(of: Void.self) { group in
105+
group.addTask {
106+
try await endpointServer.listen()
107+
}
108+
group.addTask {
109+
try await mainServer.listen()
110+
}
111+
defer { group.cancelAll() }
112+
113+
_ = try await group.next()
114+
}
115+
} catch {
116+
log.error("\(commandName) failed", metadata: ["error": "\(error)"])
117+
try? await eventLoopGroup.shutdownGracefully()
118+
RuntimeLinuxHelper.Start.exit(withError: error)
119+
}
120+
}
121+
122+
private func adjustLimits() throws {
123+
var limits = rlimit()
124+
guard getrlimit(RLIMIT_NOFILE, &limits) == 0 else {
125+
throw POSIXError(.init(rawValue: errno)!)
126+
}
127+
limits.rlim_cur = 65536
128+
limits.rlim_max = 65536
129+
guard setrlimit(RLIMIT_NOFILE, &limits) == 0 else {
130+
throw POSIXError(.init(rawValue: errno)!)
131+
}
132+
}
133+
}
134+
}

Sources/Helpers/RuntimeLinux/RuntimeLinuxHelper.swift

Lines changed: 11 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -15,140 +15,39 @@
1515
//===----------------------------------------------------------------------===//
1616

1717
import ArgumentParser
18-
import ContainerClient
1918
import ContainerLog
20-
import ContainerNetworkService
21-
import ContainerSandboxService
2219
import ContainerVersion
23-
import ContainerXPC
24-
import Containerization
25-
import ContainerizationError
26-
import Foundation
2720
import Logging
28-
import NIO
21+
import OSLog
2922

3023
@main
3124
struct RuntimeLinuxHelper: AsyncParsableCommand {
32-
static let label = "com.apple.container.runtime.container-runtime-linux"
33-
3425
static let configuration = CommandConfiguration(
3526
commandName: "container-runtime-linux",
3627
abstract: "XPC Service for managing a Linux sandbox",
37-
version: ReleaseVersion.singleLine(appName: "container-runtime-linux")
28+
version: ReleaseVersion.singleLine(appName: "container-runtime-linux"),
29+
subcommands: [
30+
Start.self
31+
]
3832
)
3933

40-
@Flag(name: .long, help: "Enable debug logging")
41-
var debug = false
42-
43-
@Option(name: .shortAndLong, help: "Sandbox UUID")
44-
var uuid: String
45-
46-
@Option(name: .shortAndLong, help: "Root directory for the sandbox")
47-
var root: String
48-
49-
var machServiceLabel: String {
50-
"\(Self.label).\(uuid)"
51-
}
52-
53-
func run() async throws {
54-
let commandName = Self._commandName
55-
let log = setupLogger()
56-
log.info("starting \(commandName)")
57-
defer {
58-
log.info("stopping \(commandName)")
59-
}
60-
61-
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
62-
do {
63-
try adjustLimits()
64-
signal(SIGPIPE, SIG_IGN)
65-
66-
log.info("configuring XPC server")
67-
68-
let interfaceStrategy: any InterfaceStrategy
69-
if #available(macOS 26, *) {
70-
interfaceStrategy = NonisolatedInterfaceStrategy(log: log)
71-
} else {
72-
interfaceStrategy = IsolatedInterfaceStrategy()
73-
}
74-
75-
nonisolated(unsafe) let anonymousConnection = xpc_connection_create(nil, nil)
76-
let server = SandboxService(
77-
root: .init(fileURLWithPath: root),
78-
interfaceStrategy: interfaceStrategy,
79-
eventLoopGroup: eventLoopGroup,
80-
connection: anonymousConnection,
81-
log: log
82-
)
83-
84-
let endpointServer = XPCServer(
85-
identifier: machServiceLabel,
86-
routes: [
87-
SandboxRoutes.createEndpoint.rawValue: server.createEndpoint
88-
],
89-
log: log
90-
)
91-
92-
let mainServer = XPCServer(
93-
connection: anonymousConnection,
94-
routes: [
95-
SandboxRoutes.bootstrap.rawValue: server.bootstrap,
96-
SandboxRoutes.createProcess.rawValue: server.createProcess,
97-
SandboxRoutes.state.rawValue: server.state,
98-
SandboxRoutes.stop.rawValue: server.stop,
99-
SandboxRoutes.kill.rawValue: server.kill,
100-
SandboxRoutes.resize.rawValue: server.resize,
101-
SandboxRoutes.wait.rawValue: server.wait,
102-
SandboxRoutes.start.rawValue: server.startProcess,
103-
SandboxRoutes.dial.rawValue: server.dial,
104-
SandboxRoutes.shutdown.rawValue: server.shutdown,
105-
],
106-
log: log
107-
)
108-
109-
log.info("starting XPC server")
110-
try await withThrowingTaskGroup(of: Void.self) { group in
111-
group.addTask {
112-
try await endpointServer.listen()
113-
}
114-
group.addTask {
115-
try await mainServer.listen()
116-
}
117-
defer { group.cancelAll() }
118-
119-
_ = try await group.next()
120-
}
121-
} catch {
122-
log.error("\(commandName) failed", metadata: ["error": "\(error)"])
123-
try? await eventLoopGroup.shutdownGracefully()
124-
RuntimeLinuxHelper.exit(withError: error)
125-
}
126-
}
127-
128-
private func setupLogger() -> Logger {
34+
package static func setupLogger(debug: Bool, metadata: [String: Logging.Logger.Metadata.Value] = [:]) -> Logging.Logger {
12935
LoggingSystem.bootstrap { label in
13036
OSLogHandler(
13137
label: label,
13238
category: "RuntimeLinuxHelper"
13339
)
13440
}
41+
13542
var log = Logger(label: "com.apple.container")
13643
if debug {
13744
log.logLevel = .debug
13845
}
139-
log[metadataKey: "uuid"] = "\(uuid)"
140-
return log
141-
}
14246

143-
private func adjustLimits() throws {
144-
var limits = rlimit()
145-
guard getrlimit(RLIMIT_NOFILE, &limits) == 0 else {
146-
throw POSIXError(.init(rawValue: errno)!)
147-
}
148-
limits.rlim_cur = 65536
149-
limits.rlim_max = 65536
150-
guard setrlimit(RLIMIT_NOFILE, &limits) == 0 else {
151-
throw POSIXError(.init(rawValue: errno)!)
47+
for (key, val) in metadata {
48+
log[metadataKey: key] = val
15249
}
50+
51+
return log
15352
}
15453
}

Sources/Services/ContainerAPIService/Containers/ContainersService.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,7 @@ public actor ContainersService {
522522
path: URL
523523
) throws {
524524
let args = [
525+
"start",
525526
"--root", path.path,
526527
"--uuid", configuration.id,
527528
"--debug",

0 commit comments

Comments
 (0)