Compare commits

...
20 Commits
Author SHA1 Message Date
Nikolay Edigaryev ea6fd814f5 Upgrade Sentry to 8.3.3 (#455) 2023-03-29 08:52:17 -04:00
Nikolay Edigaryev e72fcd9b19 tart list: show if the VM is running or not (#456)
* tart list: show if the VM is running or not

* Boolean "running" field instead of "state", similarly to "tart get"

* Re-use VMDirectory.running() in "tart get"
2023-03-29 08:51:53 -04:00
Nikolay Edigaryev 33a51b7344 tart export: make export path optional (#454) 2023-03-27 16:59:57 +00:00
fedor 5da1a085d3 Duplicate testimonials 2023-03-17 17:44:36 -04:00
Fedor KorotkovandNikolay Edigaryev f62949b6f4 Option to pass externally created serial console (#448)
* Option to pass externally created serial console

See https://github.com/cirruslabs/tart/pull/364#issuecomment-1472111742 for details

* Fixed compilation

* Apply suggestions from code review

Co-authored-by: Nikolay Edigaryev <edigaryev@gmail.com>

---------

Co-authored-by: Nikolay Edigaryev <edigaryev@gmail.com>
2023-03-17 13:52:13 +00:00
Nikolay Edigaryev d7561cab0b Graceful Softnet termination (#434) 2023-03-16 18:37:46 +04:00
fedor 066f585c53 Reformatted Serial.swift 2023-03-16 10:22:27 -04:00
d8ac36b3bd support ZVirtioConsoleDevice for linux vm (#364)
* support ZVirtioConsoleDevice for linux vm, user can control the vm via serial port with screen command

* cleanup code, fix indent

* add --serial option to tart

* remove serial in vmconfig, fix Serial.swift

---------

Co-authored-by: peternguyen93 <peternguyen9321@gmail.com>
Co-authored-by: peter <peter@starlabs.sg>
2023-03-16 10:21:13 -04:00
Fedor Korotkov 4a454a3115 Allow to choose IP resolution strategy (#446)
Haven't seen any reports of the `arp` vs DHCP issues so I think it's reasonable to remove the warning and allow to customize the resolution strategy.
2023-03-16 17:41:54 +04:00
fedor 94e9425db7 Disable building pkg 2023-03-07 07:43:23 -08:00
Alexander Zaytsev 0ab0fe23c5 Add Transloadit as a Tart user (#442) 2023-03-07 19:02:09 +04:00
Fedor Korotkov 0a970462e8 Revert most of the release logic after 1.0.0 (#443)
Logic for building .tar.gz should be the same as for 1.0.0 and only after that a .pkg is build to make sure it's not interfering. Let's try this before disabling building .pkg all together.
2023-03-07 19:01:34 +04:00
Fedor Korotkov 5b8d1d1168 Switch back to .tar.gz (#440)
Since trying to notarized the release archive as mentioned [here](https://github.com/cirruslabs/tart/pull/415#issuecomment-1450545065) seems breaking the validation.
2023-03-06 23:59:01 -05:00
Fedor Korotkov 97c63bcc5b Remove binary attributes (#439)
* Remove all binary attributes

To fix "code has no resources but signature indicates they must be present" which is caused by attributes

* Also verify signature for testing

* Do everything at once
2023-03-06 21:46:09 +04:00
fedor 6409d53814 [skip ci] Fixed links in docs 2023-03-05 11:36:10 -08:00
fedor 3ce58e8174 Fixed pkg path 2023-03-03 17:56:44 -08:00
Fedor Korotkov 5e44116b69 Build Package installer and notarize release archive (#436)
* Build Package Installer

* Fixed typo

* Notarize archive

* Try lowercase

* Use zip to satisfy notarization

* Always upload dist

* Don't duplicate binaries

* Fancier Homebrew install block

* Can't stable zip

* Renamed artifacts instruction

* Fixed config

* upload pkg

* Lowercase

* don't package app-structure
2023-03-03 20:42:22 -05:00
Fedor Korotkov fda5ec42e7 Enable RSS feed for blog (#437)
Fixes #435
2023-03-02 18:37:37 +00:00
Fedor Korotkov 80e057008b Clarify license for different types of cores (#432) 2023-03-01 20:45:05 +04:00
Fedor Korotkov dbc1632fb4 Removed link to Orchard (#430)
Since the repo is not yet public
2023-03-01 08:21:04 +04:00
25 changed files with 267 additions and 73 deletions
+5 -5
View File
@@ -4,12 +4,12 @@ set -e
export VERSION="${CIRRUS_TAG:-0}"
mkdir -p .ci/pkg/Tart.app/Contents/MacOS
cp .build/arm64-apple-macosx/debug/tart .ci/pkg/Tart.app/Contents/MacOS/Tart
cp Resources/embedded.provisionprofile .ci/pkg/Tart.app/Contents/embedded.provisionprofile
pkgbuild --root .ci/pkg/Tart.app --identifier com.github.cirruslabs.tart --version $VERSION \
mkdir -p .ci/pkg/
cp .build/arm64-apple-macosx/debug/tart .ci/pkg/tart
cp Resources/embedded.provisionprofile .ci/pkg/embedded.provisionprofile
pkgbuild --root .ci/pkg/ --identifier com.github.cirruslabs.tart --version $VERSION \
--scripts .ci/pkg/scripts \
--install-location "/Library/Application Support/Tart/Tart.app" \
--install-location "/Library/Application Support/Tart" \
--sign "Developer ID Installer: Cirrus Labs, Inc. (9M2P8L4D89)" \
"./.ci/Tart-$VERSION.pkg"
xcrun notarytool submit "./.ci/Tart-$VERSION.pkg" --keychain-profile "notarytool" --wait
+6 -1
View File
@@ -2,7 +2,12 @@
set -e
# fix structure
mkdir -p "$2/tart.app/Contents/MacOS"
mv "$2/tart" "$2/tart.app/Contents/MacOS/tart"
mv "$2/embedded.provisionprofile" "$2/tart.app/Contents/embedded.provisionprofile"
echo "#!/bin/sh" > /usr/local/bin/tart
echo "exec '$2/Contents/MacOS/Tart' \"\$@\"" >> /usr/local/bin/tart
echo "exec '$2/tart.app/Contents/MacOS/tart' \"\$@\"" >> /usr/local/bin/tart
chmod +x /usr/local/bin/tart
+34
View File
@@ -47,6 +47,40 @@ task:
binary_artifacts:
path: .build/debug/tart
task:
name: Release (Dry Run)
only_if: $CIRRUS_TAG == '' && ($CIRRUS_USER_PERMISSION == 'write' || $CIRRUS_USER_PERMISSION == 'admin')
depends_on:
- lint
- build
macos_instance:
image: ghcr.io/cirruslabs/macos-ventura-xcode:latest
env:
MACOS_CERTIFICATE: ENCRYPTED[552b9d275d1c2bdbc1bff778b104a8f9a53cbd0d59344d4b7f6d0ca3c811a5cefb97bef9ba0ef31c219cb07bdacdd2c2]
AC_PASSWORD: ENCRYPTED[4a761023e7e06fe2eb350c8b6e8e7ca961af193cb9ba47605f25f1d353abc3142606f412e405be48fd897a78787ea8c2]
GITHUB_TOKEN: ENCRYPTED[!98ace8259c6024da912c14d5a3c5c6aac186890a8d4819fad78f3e0c41a4e0cd3a2537dd6e91493952fb056fa434be7c!]
GORELEASER_KEY: ENCRYPTED[!9b80b6ef684ceaf40edd4c7af93014ee156c8aba7e6e5795f41c482729887b5c31f36b651491d790f1f668670888d9fd!]
setup_script:
- cd $HOME
- echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12
- security create-keychain -p password101 build.keychain
- security default-keychain -s build.keychain
- security unlock-keychain -p password101 build.keychain
- security import certificate.p12 -k build.keychain -P password101 -T /usr/bin/codesign -T /usr/bin/pkgbuild
- security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k password101 build.keychain
- xcrun notarytool store-credentials "notarytool" --apple-id "hello@cirruslabs.org" --team-id "9M2P8L4D89" --password $AC_PASSWORD
install_script:
- brew install go goreleaser/tap/goreleaser-pro getsentry/tools/sentry-cli
- brew install mitchellh/gon/gon
info_script:
- security find-identity -v
- xcodebuild -version
- swift -version
goreleaser_script: goreleaser release --skip-publish --snapshot --clean
always:
dist_artifacts:
path: "dist/*"
task:
name: Release
only_if: $CIRRUS_TAG != ''
+3 -3
View File
@@ -12,9 +12,9 @@ builds:
goarch:
- arm64
prebuilt:
path: .build/arm64-apple-macosx/debug/tart
path: .build/arm64-apple-macosx/debug/tart
hooks:
post:
post:
- gon gon.hcl
archives:
@@ -44,7 +44,7 @@ brews:
- "cirruslabs/cli/softnet"
install: |
libexec.install Dir["*"]
bin.write_exec_script "#{libexec}/Tart.app/Contents/MacOS/tart"
bin.write_exec_script "#{libexec}/tart.app/Contents/MacOS/tart"
custom_block: |
depends_on :macos => :monterey
+2 -2
View File
@@ -23,8 +23,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/getsentry/sentry-cocoa",
"state" : {
"revision" : "c3c19e29f775ee95b77aa3168f3e2fd6c20deba6",
"version" : "7.31.3"
"revision" : "ac224c437a3070ffe34460137ac8761eddaf2852",
"version" : "8.3.3"
}
},
{
+1 -1
View File
@@ -18,7 +18,7 @@ let package = Package(
.package(url: "https://github.com/antlr/antlr4", branch: "dev"),
.package(url: "https://github.com/apple/swift-atomics.git", .upToNextMajor(from: "1.0.0")),
.package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.50.6"),
.package(url: "https://github.com/getsentry/sentry-cocoa", from: "7.31.3"),
.package(url: "https://github.com/getsentry/sentry-cocoa", from: "8.3.3"),
.package(url: "https://github.com/cfilipov/TextTable", branch: "master"),
],
targets: [
+3
View File
@@ -37,6 +37,9 @@ Many more companies are using Tart in their internal setups. Here are a few of t
<a href="https://symflower.com/" target=_blank>
<img src="https://github.com/cirruslabs/tart/raw/main/Resources/Users/Symflower.png" height="65"/>
</a>
<a href="https://transloadit.com/" target=_blank>
<img src="https://github.com/cirruslabs/tart/raw/main/Resources/Users/Transloadit.png" height="65"/>
</a>
</p>
**Note:** If your company or project is using Tart please consider [adding yourself to the list above](/Resources/Users/HowToAddYourself.md).
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

+31 -3
View File
@@ -1,16 +1,44 @@
import ArgumentParser
import Foundation
struct Export: AsyncParsableCommand {
static var configuration = CommandConfiguration(abstract: "Export VM to a file")
static var configuration = CommandConfiguration(abstract: "Export VM to a compressed .tvm file")
@Argument(help: "Source VM name.")
var name: String
@Argument(help: "Path to the destination file.")
var path: String
var path: String?
func run() async throws {
let correctedPath: String
if let path = path {
correctedPath = path
} else {
correctedPath = "\(name).tvm"
if FileManager.default.fileExists(atPath: correctedPath) {
while true {
if userWantsOverwrite(correctedPath) {
break
} else {
return
}
}
}
}
print("exporting...")
try VMStorageHelper.open(name).exportToArchive(path: path)
try VMStorageHelper.open(name).exportToArchive(path: correctedPath)
}
func userWantsOverwrite(_ filename: String) -> Bool {
print("file \(filename) already exists, are you sure you want to overwrite it? (yes, [no])? ", terminator: "")
let answer = readLine()!
return answer == "yes"
}
}
+2 -2
View File
@@ -23,9 +23,9 @@ struct Get: AsyncParsableCommand {
let vmConfig = try VMConfig(fromURL: vmDir.configURL)
let diskSizeInGb = try vmDir.sizeGB()
let memorySizeInMb = vmConfig.memorySize / 1024 / 1024
let running = try PIDLock(lockURL: vmDir.configURL).pid() > 0
let info = VMInfo(CPU: vmConfig.cpuCount, Memory: memorySizeInMb, Disk: diskSizeInGb, Display: vmConfig.display.description, Running: running)
let info = VMInfo(CPU: vmConfig.cpuCount, Memory: memorySizeInMb, Disk: diskSizeInGb,
Display: vmConfig.display.description, Running: try vmDir.running())
print(format.renderSingle(info))
}
}
+28 -28
View File
@@ -4,6 +4,12 @@ import Network
import SystemConfiguration
import Sentry
enum IPResolutionStrategy: String, ExpressibleByArgument, CaseIterable {
case dhcp, arp
private(set) static var allValueStrings: [String] = Format.allCases.map { "\($0)"}
}
struct IP: AsyncParsableCommand {
static var configuration = CommandConfiguration(abstract: "Get VM's IP address")
@@ -13,46 +19,40 @@ struct IP: AsyncParsableCommand {
@Option(help: "Number of seconds to wait for a potential VM booting")
var wait: UInt16 = 0
@Option(help: ArgumentHelp("Strategy for resolving IP address: dhcp or arp",
discussion: """
By default, Tart is looking up and parsing DHCP lease file to determine the IP of the VM.\n
This method is fast and the most reliable but only returns local IP adresses.\n
Alternatively, Tart can call external `arp` executable and parse it's output.\n
In case of enabled Bridged Networking this method will return VM's IP address on the network interface used for Bridged Networking.\n
Note that `arp` strategy won't work for VMs using `--net-softnet`.
"""))
var resolver: IPResolutionStrategy = .dhcp
func run() async throws {
let vmDir = try VMStorageLocal().open(name)
let vmConfig = try VMConfig.init(fromURL: vmDir.configURL)
let vmMACAddress = MACAddress(fromString: vmConfig.macAddress.string)!
guard let ipViaDHCP = try await IP.resolveIP(vmMACAddress, secondsToWait: wait) else {
guard let ip = try await IP.resolveIP(vmMACAddress, resolutionStrategy: resolver, secondsToWait: wait) else {
throw RuntimeError.NoIPAddressFound("no IP address found, is your VM running?")
}
let arpCache = try ARPCache()
if let ipViaARP = try arpCache.ResolveMACAddress(macAddress: vmMACAddress), ipViaARP != ipViaDHCP {
// Capture the warning into Sentry
SentrySDK.capture(message: "DHCP lease and ARP cache entries for a single MAC address differ") { scope in
scope.setLevel(.warning)
scope.setContext(value: [
"MAC address": vmMACAddress,
"IP via ARP": ipViaARP,
"IP via DHCP": ipViaDHCP,
], key: "Address conflict details")
scope.add(Attachment(path: "/var/db/dhcpd_leases", filename: "dhcpd_leases.txt", contentType: "text/plain"))
scope.add(Attachment(data: arpCache.arpCommandOutput, filename: "arp-an-output.txt", contentType: "text/plain"))
}
fputs("WARNING: DHCP lease and ARP cache entries for MAC address \(vmMACAddress) differ: "
+ "got \(ipViaDHCP) and \(ipViaARP) respectively, consider reporting this case to"
+ " https://github.com/cirruslabs/tart/issues/172\n", stderr)
}
print(ipViaDHCP)
print(ip)
}
static public func resolveIP(_ vmMACAddress: MACAddress, secondsToWait: UInt16) async throws -> IPv4Address? {
static public func resolveIP(_ vmMACAddress: MACAddress, resolutionStrategy: IPResolutionStrategy = .dhcp, secondsToWait: UInt16 = 0) async throws -> IPv4Address? {
let waitUntil = Calendar.current.date(byAdding: .second, value: Int(secondsToWait), to: Date.now)!
repeat {
if let leases = try Leases(), let ip = try leases.resolveMACAddress(macAddress: vmMACAddress) {
return ip
switch resolutionStrategy {
case .arp:
if let ip = try ARPCache().ResolveMACAddress(macAddress: vmMACAddress) {
return ip
}
case .dhcp:
if let leases = try Leases(), let ip = try leases.ResolveMACAddress(macAddress: vmMACAddress) {
return ip
}
}
// wait a second
+1 -1
View File
@@ -2,7 +2,7 @@ import ArgumentParser
import Foundation
struct Import: AsyncParsableCommand {
static var configuration = CommandConfiguration(abstract: "Import VM from a file")
static var configuration = CommandConfiguration(abstract: "Import VM from a compressed .tvm file")
@Argument(help: "Path to a file created with \"tart export\".")
var path: String
+5 -2
View File
@@ -6,6 +6,7 @@ fileprivate struct VMInfo: Encodable {
let Source: String
let Name: String
let Size: Int
let Running: Bool
}
struct List: AsyncParsableCommand {
@@ -32,17 +33,19 @@ struct List: AsyncParsableCommand {
func run() async throws {
var infos: [VMInfo] = []
if source == nil || source == "local" {
infos += sortedInfos(try VMStorageLocal().list().map { (name, vmDir) in
try VMInfo(Source: "local", Name: name, Size: vmDir.sizeGB())
try VMInfo(Source: "local", Name: name, Size: vmDir.sizeGB(), Running: vmDir.running())
})
}
if source == nil || source == "oci" {
infos += sortedInfos(try VMStorageOCI().list().map { (name, vmDir, _) in
try VMInfo(Source: "oci", Name: name, Size: vmDir.sizeGB())
try VMInfo(Source: "oci", Name: name, Size: vmDir.sizeGB(), Running: vmDir.running())
})
}
if (quiet) {
for info in infos {
print(info.Name)
+50 -4
View File
@@ -17,13 +17,24 @@ struct Run: AsyncParsableCommand {
@Flag(help: ArgumentHelp(
"Don't open a UI window.",
discussion: "Useful for integrating Tart VMs into other tools.\nUse `tart ip` in order to get an IP for SSHing or VNCing into the VM."))
discussion: "Useful for integrating Tart VMs into other tools.\nUse `tart ip` in order to get an IP for SSHing or VNCing into the VM."))
var noGraphics: Bool = false
@Flag(help: ArgumentHelp(
"Open serial console in /dev/ttySXX",
discussion: "Useful for debugging Linux Kernel."))
var serial: Bool = false
@Option(help: ArgumentHelp(
"Attach an externally created serial console",
discussion: "Alternative to `--serial` flag for programmatic integrations."
))
var serialPath: String?
@Flag(help: "Force open a UI window, even when VNC is enabled.")
var graphics: Bool = false
@Flag(help: "Boot into recovery mode")
@Flag(help: "Boot into recovery mode")
var recovery: Bool = false
@Flag(help: ArgumentHelp(
@@ -118,11 +129,30 @@ struct Run: AsyncParsableCommand {
}
}
var serialPorts: [VZSerialPortConfiguration] = []
if serial {
let tty_fd = createPTY()
if (tty_fd < 0) {
throw RuntimeError.VMConfigurationError("Failed to create PTY")
}
let tty_read = FileHandle.init(fileDescriptor: tty_fd)
let tty_write = FileHandle.init(fileDescriptor: tty_fd)
serialPorts.append(createSerialPortConfiguration(tty_read, tty_write))
} else if serialPath != nil {
let tty_read = FileHandle.init(forReadingAtPath: serialPath!)
let tty_write = FileHandle.init(forWritingAtPath: serialPath!)
if (tty_read == nil || tty_write == nil) {
throw RuntimeError.VMConfigurationError("Failed to open PTY")
}
serialPorts.append(createSerialPortConfiguration(tty_read!, tty_write!))
}
vm = try VM(
vmDir: vmDir,
network: userSpecifiedNetwork(vmDir: vmDir) ?? NetworkShared(),
additionalDiskAttachments: additionalDiskAttachments,
directorySharingDevices: directoryShares() + rosettaDirectoryShare()
directorySharingDevices: directoryShares() + rosettaDirectoryShare(),
serialPorts: serialPorts
)
let vncImpl: VNC? = try {
@@ -198,6 +228,16 @@ struct Run: AsyncParsableCommand {
}
}
private func createSerialPortConfiguration(_ tty_read: FileHandle, _ tty_write: FileHandle) -> VZVirtioConsoleDeviceSerialPortConfiguration {
let serialPortConfiguration = VZVirtioConsoleDeviceSerialPortConfiguration()
let serialPortAttachment = VZFileHandleSerialPortAttachment(
fileHandleForReading: tty_read,
fileHandleForWriting: tty_write)
serialPortConfiguration.attachment = serialPortAttachment
return serialPortConfiguration
}
func isInteractiveSession() -> Bool {
isatty(STDOUT_FILENO) == 1
}
@@ -357,7 +397,13 @@ struct Run: AsyncParsableCommand {
VMView(vm: vm!).onAppear {
NSWindow.allowsAutomaticWindowTabbing = false
}.onDisappear {
NSApplication.shared.terminate(self)
let ret = kill(getpid(), SIGINT)
if ret != 0 {
// Fallback to the old termination method that doesn't
// propagate the cancellation to Task's in case graceful
// termination via kill(2) is not successful
NSApplication.shared.terminate(self)
}
}
}.frame(width: CGFloat(vm!.config.display.width), height: CGFloat(vm!.config.display.height))
}.commands {
@@ -67,7 +67,7 @@ struct ARPCache {
self.arpCommandOutput = arpCommandOutput
}
func ResolveMACAddress(macAddress: MACAddress, bridgeOnly: Bool = true) throws -> IPv4Address? {
func ResolveMACAddress(macAddress: MACAddress) throws -> IPv4Address? {
let lines = String(decoding: arpCommandOutput, as: UTF8.self)
.trimmingCharacters(in: .whitespacesAndNewlines)
.components(separatedBy: "\n")
@@ -95,11 +95,6 @@ struct ARPCache {
throw ARPCommandYieldedInvalidOutputError(explanation: "failed to parse MAC address \(rawMAC)")
}
let interface = try match.getCaptureGroup(name: "interface", for: line)
if bridgeOnly && !interface.starts(with: "bridge") {
continue
}
if macAddress == mac {
return ip
}
+1 -1
View File
@@ -107,7 +107,7 @@ class Leases {
return rawLeases
}
func resolveMACAddress(macAddress: MACAddress) throws -> IPv4Address? {
func ResolveMACAddress(macAddress: MACAddress) throws -> IPv4Address? {
leases[macAddress]?.ip
}
}
+44
View File
@@ -0,0 +1,44 @@
import Foundation
func createPTY() -> Int32 {
var tty_fd: Int32 = -1
var sfd: Int32 = -1
var termios_ = termios()
let tty_path = UnsafeMutablePointer<CChar>.allocate(capacity: 1024)
var res = openpty(&tty_fd, &sfd, tty_path, nil, nil);
if (res < 0) {
perror("openpty error")
return -1
}
// close slave file descriptor
close(sfd)
res = fcntl(tty_fd, F_GETFL)
if (res < 0) {
perror("fcntl F_GETFL error")
return res
}
// set serial nonblocking
res = fcntl(tty_fd, F_SETFL, res | O_NONBLOCK)
if (res < 0) {
perror("fcntl F_SETFL O_NONBLOCK error")
return res
}
// set baudrate to 115200
tcgetattr(tty_fd, &termios_)
cfsetispeed(&termios_, speed_t(B115200))
cfsetospeed(&termios_, speed_t(B115200))
if (tcsetattr(tty_fd, TCSANOW, &termios_) != 0) {
perror("tcsetattr error")
return -1
}
print("Successfully open pty \(String(cString: tty_path))")
tty_path.deallocate()
return tty_fd
}
+13 -5
View File
@@ -40,7 +40,8 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
init(vmDir: VMDirectory,
network: Network = NetworkShared(),
additionalDiskAttachments: [VZDiskImageStorageDeviceAttachment] = [],
directorySharingDevices: [VZDirectorySharingDeviceConfiguration] = []
directorySharingDevices: [VZDirectorySharingDeviceConfiguration] = [],
serialPorts: [VZSerialPortConfiguration] = []
) throws {
name = vmDir.name
config = try VMConfig.init(fromURL: vmDir.configURL)
@@ -54,7 +55,8 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
let configuration = try Self.craftConfiguration(diskURL: vmDir.diskURL,
nvramURL: vmDir.nvramURL, vmConfig: config,
network: network, additionalDiskAttachments: additionalDiskAttachments,
directorySharingDevices: directorySharingDevices
directorySharingDevices: directorySharingDevices,
serialPorts: serialPorts
)
virtualMachine = VZVirtualMachine(configuration: configuration)
@@ -132,7 +134,8 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
diskSizeGB: UInt16,
network: Network = NetworkShared(),
additionalDiskAttachments: [VZDiskImageStorageDeviceAttachment] = [],
directorySharingDevices: [VZDirectorySharingDeviceConfiguration] = []
directorySharingDevices: [VZDirectorySharingDeviceConfiguration] = [],
serialPorts: [VZSerialPortConfiguration] = []
) async throws {
var ipswURL = ipswURL
@@ -179,7 +182,8 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
let configuration = try Self.craftConfiguration(diskURL: vmDir.diskURL, nvramURL: vmDir.nvramURL,
vmConfig: config, network: network,
additionalDiskAttachments: additionalDiskAttachments,
directorySharingDevices: directorySharingDevices
directorySharingDevices: directorySharingDevices,
serialPorts: serialPorts
)
virtualMachine = VZVirtualMachine(configuration: configuration)
@@ -261,7 +265,8 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
vmConfig: VMConfig,
network: Network = NetworkShared(),
additionalDiskAttachments: [VZDiskImageStorageDeviceAttachment],
directorySharingDevices: [VZDirectorySharingDeviceConfiguration]
directorySharingDevices: [VZDirectorySharingDeviceConfiguration],
serialPorts: [VZSerialPortConfiguration]
) throws -> VZVirtualMachineConfiguration {
let configuration = VZVirtualMachineConfiguration()
@@ -308,6 +313,9 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
// Directory sharing devices
configuration.directorySharingDevices = directorySharingDevices
// Serial Port
configuration.serialPorts = serialPorts
try configuration.validate()
return configuration
+4
View File
@@ -26,6 +26,10 @@ struct VMDirectory: Prunable {
baseURL
}
func running() throws -> Bool {
try PIDLock(lockURL: configURL).pid() != 0
}
static func temporary() throws -> VMDirectory {
let tmpDir = try Config().tartTmpDir.appendingPathComponent(UUID().uuidString)
try FileManager.default.createDirectory(at: tmpDir, withIntermediateDirectories: false)
+3
View File
@@ -41,6 +41,7 @@ extension Error {
}
enum RuntimeError : Error {
case VMConfigurationError(_ message: String)
case VMDoesNotExist(name: String)
case VMMissingFiles(_ message: String)
case VMNotRunning(_ message: String)
@@ -65,6 +66,8 @@ protocol HasExitCode {
extension RuntimeError : CustomStringConvertible {
public var description: String {
switch self {
case .VMConfigurationError(let message):
return message
case .VMDoesNotExist(let name):
return "the specified VM \"\(name)\" does not exist"
case .VMMissingFiles(let message):
@@ -12,7 +12,7 @@ final class MACAddressResolverTests: XCTestCase {
""")
XCTAssertEqual(IPv4Address("1.2.3.4"),
try leases.resolveMACAddress(macAddress: MACAddress(fromString: "00:11:22:33:44:55")!))
try leases.ResolveMACAddress(macAddress: MACAddress(fromString: "00:11:22:33:44:55")!))
}
func testMultipleEntries() throws {
@@ -28,8 +28,8 @@ final class MACAddressResolverTests: XCTestCase {
""")
XCTAssertEqual(IPv4Address("1.2.3.4"),
try leases.resolveMACAddress(macAddress: MACAddress(fromString: "00:11:22:33:44:55")!))
try leases.ResolveMACAddress(macAddress: MACAddress(fromString: "00:11:22:33:44:55")!))
XCTAssertEqual(IPv4Address("5.6.7.8"),
try leases.resolveMACAddress(macAddress: MACAddress(fromString: "AA:BB:CC:DD:EE:FF")!))
try leases.ResolveMACAddress(macAddress: MACAddress(fromString: "AA:BB:CC:DD:EE:FF")!))
}
}
+11
View File
@@ -4,6 +4,17 @@ Tart already powers several CI services mentioned above including our own [Cirru
For services that haven't leveraged Tart yet, we offer fully managed runners via a monthly subscription.
*Cirrus Runners* is the fastest way to get your current CI workflows to benefit from Apple Silicon hardware. No need to manage infrastructure or migrate to another CI provider.
## Testimonials from customers
Sebastian Jachec, Mobile Engineer at [Daybridge](https://www.daybridge.com/).
> It’s been plain-sailing with the Cirrus Runners — they’ve been great! They’re consistently 60+% faster on workflows that we previously used Github Actions’ macOS runners for.
Max Lapides, Senior Mobile Engineer at [Tonal](https://www.tonal.com/).
> Previously, we were using the GitHub‑hosted macOS runners and our iOS build took ~30 minutes. Now with Cirrus Runners, the iOS build only takes ~12 minutes. That’s a huge boost to our productivity, and for only $150/month per runner it is much less expensive too.
## Configuring Cirrus Runners
Configuring Cirrus Runners for GitHub Actions is as simple as installing [Cirrus Runners App](https://github.com/apps/cirrus-runners).
+9 -3
View File
@@ -3,9 +3,15 @@ hide:
- navigation
---
Both [Tart Virtualization](https://github.com/cirruslabs/tart) and [Orchard Orchestration](https://github.com/cirruslabs/orchard)
Both [Tart Virtualization](https://github.com/cirruslabs/tart) and **Orchard Orchestration** (coming soon)
are licensed under [Fair Source 100 License](https://fair.io/). Usage on personal computers including personal workstations is royalty-free,
but organizations that exceed a certain number of server installations utilizing 100 CPU cores will be required to obtain a paid sponsorship.
but organizations that exceed a certain number of server installations utilizing 100 CPU cores will be required to obtain a paid sponsorship.
??? note "Performance and Efficiency Cores"
The virtual CPU cores in Tart VMs do not differentiate between the high-performance and high-efficient cores
of the host CPU. Instead, Tart VMs automatically alternate between these types of cores depending on the workload
being executed within the virtual machines. As a result, both performance and energy-efficient cores of the host CPU
are treated equally in terms of licensing.
# Sponsorships
@@ -27,7 +33,7 @@ Our support team will answer it by replying to the issue or by updating the docu
# Priority Support
In addition to the general support we provide a *Priority Support*with guaranteed response times included in all the paid sponsorships .
In addition to the general support we provide a *Priority Support* with guaranteed response times included in all the paid sponsorships .
| Severity | Support Impact | First Response Time SLA | Hours | How to Submit |
|----------|-----------------------------------------------------------------------------------------------|-------------------------|-------|--------------------------------------------------------------------------------------------------|
+3 -3
View File
@@ -169,7 +169,7 @@
<figcaption class="md-typeset">
<p>
Tart powers several continuous integration systems including
<a href="/github-actions"
<a href="/integrations/github-actions"
>on&#8209;demand GitHub Actions Runners</a
>
and
@@ -261,7 +261,7 @@
<hr/>
<cite>
It&rsquo;s been plain-sailing with the
<a href="/github-actions">Cirrus Runners</a>&nbsp;&mdash;
<a href="/integrations/github-actions">Cirrus Runners</a>&nbsp;&mdash;
they&rsquo;ve been great! They&rsquo;re consistently&nbsp;60+%
faster on&nbsp;workflows that we&nbsp;previously used Github
Actions&rsquo; macOS runners for.
@@ -310,7 +310,7 @@
<cite>
Previously, we were using the GitHub&#8209;hosted macOS runners
and our iOS build took ~30&nbsp;minutes. Now with
<a href="/github-actions">Cirrus Runners</a>, the iOS build only
<a href="/integrations/github-actions">Cirrus Runners</a>, the iOS build only
takes ~12&nbsp;minutes. That’s a huge boost to our productivity,
and for only $150/month per runner it is much less expensive too.
</cite>
+4
View File
@@ -42,6 +42,10 @@ extra_css:
plugins:
- blog
- rss:
match_path: blog/posts/.*
date_from_meta:
as_creation: date
- social
- search
- minify