//===----------------------------------------------------------------------===// // Copyright © 2025-2026 Apple Inc. and the container project authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //===----------------------------------------------------------------------===// import ArgumentParser import ContainerAPIClient import ContainerPlugin import ContainerResource import Containerization import ContainerizationOS import Foundation import Logging extension Application { public struct SystemStop: AsyncLoggableCommand { private static let stopTimeoutSeconds: Int32 = 5 private static let shutdownTimeoutSeconds: Int32 = 20 public static let configuration = CommandConfiguration( commandName: "stop", abstract: "Stop all `container` services" ) @Option(name: .shortAndLong, help: "Launchd prefix for services") var prefix: String = "com.apple.container." @OptionGroup public var logOptions: Flags.Logging public init() {} public func run() async throws { let log = Logger( label: "com.apple.container.cli", factory: { label in StreamLogHandler.standardOutput(label: label) } ) let launchdDomainString = try ServiceManager.getDomainString() let fullLabel = "\(launchdDomainString)/\(prefix)apiserver" var running = true do { log.info("checking if APIServer is alive") _ = try await ClientHealthCheck.ping(timeout: .seconds(5)) } catch { log.info("APIServer health check failed, skipping bootout") running = false } if running { let client = ContainerClient() log.info("stopping containers", metadata: ["stopTimeoutSeconds": "\(Self.stopTimeoutSeconds)"]) do { let containers = try await client.list().map { $0.id } let opts = ContainerStopOptions(timeoutInSeconds: Self.stopTimeoutSeconds, signal: nil) try await ContainerStop.stopContainers( client: client, containers: containers, stopOptions: opts, ) } catch { log.warning("failed to stop all containers", metadata: ["error": "\(error)"]) } log.info("waiting for containers to exit") do { for _ in 0..