mirror of
https://github.com/cirruslabs/tart.git
synced 2026-10-21 09:42:13 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f62949b6f4 | ||
|
|
d7561cab0b | ||
|
|
066f585c53 | ||
|
|
d8ac36b3bd | ||
|
|
4a454a3115 | ||
|
|
94e9425db7 | ||
|
|
0ab0fe23c5 | ||
|
|
0a970462e8 | ||
|
|
5b8d1d1168 | ||
|
|
97c63bcc5b | ||
|
|
6409d53814 | ||
|
|
3ce58e8174 | ||
|
|
5e44116b69 | ||
|
|
fda5ec42e7 | ||
|
|
80e057008b | ||
|
|
dbc1632fb4 |
+5
-5
@@ -4,12 +4,12 @@ set -e
|
|||||||
|
|
||||||
export VERSION="${CIRRUS_TAG:-0}"
|
export VERSION="${CIRRUS_TAG:-0}"
|
||||||
|
|
||||||
mkdir -p .ci/pkg/Tart.app/Contents/MacOS
|
mkdir -p .ci/pkg/
|
||||||
cp .build/arm64-apple-macosx/debug/tart .ci/pkg/Tart.app/Contents/MacOS/Tart
|
cp .build/arm64-apple-macosx/debug/tart .ci/pkg/tart
|
||||||
cp Resources/embedded.provisionprofile .ci/pkg/Tart.app/Contents/embedded.provisionprofile
|
cp Resources/embedded.provisionprofile .ci/pkg/embedded.provisionprofile
|
||||||
pkgbuild --root .ci/pkg/Tart.app --identifier com.github.cirruslabs.tart --version $VERSION \
|
pkgbuild --root .ci/pkg/ --identifier com.github.cirruslabs.tart --version $VERSION \
|
||||||
--scripts .ci/pkg/scripts \
|
--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)" \
|
--sign "Developer ID Installer: Cirrus Labs, Inc. (9M2P8L4D89)" \
|
||||||
"./.ci/Tart-$VERSION.pkg"
|
"./.ci/Tart-$VERSION.pkg"
|
||||||
xcrun notarytool submit "./.ci/Tart-$VERSION.pkg" --keychain-profile "notarytool" --wait
|
xcrun notarytool submit "./.ci/Tart-$VERSION.pkg" --keychain-profile "notarytool" --wait
|
||||||
|
|||||||
@@ -2,7 +2,12 @@
|
|||||||
|
|
||||||
set -e
|
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 "#!/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
|
chmod +x /usr/local/bin/tart
|
||||||
|
|||||||
+34
@@ -47,6 +47,40 @@ task:
|
|||||||
binary_artifacts:
|
binary_artifacts:
|
||||||
path: .build/debug/tart
|
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:
|
task:
|
||||||
name: Release
|
name: Release
|
||||||
only_if: $CIRRUS_TAG != ''
|
only_if: $CIRRUS_TAG != ''
|
||||||
|
|||||||
+3
-3
@@ -12,9 +12,9 @@ builds:
|
|||||||
goarch:
|
goarch:
|
||||||
- arm64
|
- arm64
|
||||||
prebuilt:
|
prebuilt:
|
||||||
path: .build/arm64-apple-macosx/debug/tart
|
path: .build/arm64-apple-macosx/debug/tart
|
||||||
hooks:
|
hooks:
|
||||||
post:
|
post:
|
||||||
- gon gon.hcl
|
- gon gon.hcl
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
@@ -44,7 +44,7 @@ brews:
|
|||||||
- "cirruslabs/cli/softnet"
|
- "cirruslabs/cli/softnet"
|
||||||
install: |
|
install: |
|
||||||
libexec.install Dir["*"]
|
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: |
|
custom_block: |
|
||||||
depends_on :macos => :monterey
|
depends_on :macos => :monterey
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
<a href="https://symflower.com/" target=_blank>
|
||||||
<img src="https://github.com/cirruslabs/tart/raw/main/Resources/Users/Symflower.png" height="65"/>
|
<img src="https://github.com/cirruslabs/tart/raw/main/Resources/Users/Symflower.png" height="65"/>
|
||||||
</a>
|
</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>
|
</p>
|
||||||
|
|
||||||
**Note:** If your company or project is using Tart please consider [adding yourself to the list above](/Resources/Users/HowToAddYourself.md).
|
**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 |
@@ -4,6 +4,12 @@ import Network
|
|||||||
import SystemConfiguration
|
import SystemConfiguration
|
||||||
import Sentry
|
import Sentry
|
||||||
|
|
||||||
|
enum IPResolutionStrategy: String, ExpressibleByArgument, CaseIterable {
|
||||||
|
case dhcp, arp
|
||||||
|
|
||||||
|
private(set) static var allValueStrings: [String] = Format.allCases.map { "\($0)"}
|
||||||
|
}
|
||||||
|
|
||||||
struct IP: AsyncParsableCommand {
|
struct IP: AsyncParsableCommand {
|
||||||
static var configuration = CommandConfiguration(abstract: "Get VM's IP address")
|
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")
|
@Option(help: "Number of seconds to wait for a potential VM booting")
|
||||||
var wait: UInt16 = 0
|
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 {
|
func run() async throws {
|
||||||
let vmDir = try VMStorageLocal().open(name)
|
let vmDir = try VMStorageLocal().open(name)
|
||||||
let vmConfig = try VMConfig.init(fromURL: vmDir.configURL)
|
let vmConfig = try VMConfig.init(fromURL: vmDir.configURL)
|
||||||
let vmMACAddress = MACAddress(fromString: vmConfig.macAddress.string)!
|
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?")
|
throw RuntimeError.NoIPAddressFound("no IP address found, is your VM running?")
|
||||||
}
|
}
|
||||||
|
print(ip)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)!
|
let waitUntil = Calendar.current.date(byAdding: .second, value: Int(secondsToWait), to: Date.now)!
|
||||||
|
|
||||||
repeat {
|
repeat {
|
||||||
if let leases = try Leases(), let ip = try leases.resolveMACAddress(macAddress: vmMACAddress) {
|
switch resolutionStrategy {
|
||||||
return ip
|
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
|
// wait a second
|
||||||
|
|||||||
@@ -17,13 +17,24 @@ struct Run: AsyncParsableCommand {
|
|||||||
|
|
||||||
@Flag(help: ArgumentHelp(
|
@Flag(help: ArgumentHelp(
|
||||||
"Don't open a UI window.",
|
"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
|
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.")
|
@Flag(help: "Force open a UI window, even when VNC is enabled.")
|
||||||
var graphics: Bool = false
|
var graphics: Bool = false
|
||||||
|
|
||||||
@Flag(help: "Boot into recovery mode")
|
@Flag(help: "Boot into recovery mode")
|
||||||
var recovery: Bool = false
|
var recovery: Bool = false
|
||||||
|
|
||||||
@Flag(help: ArgumentHelp(
|
@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(
|
vm = try VM(
|
||||||
vmDir: vmDir,
|
vmDir: vmDir,
|
||||||
network: userSpecifiedNetwork(vmDir: vmDir) ?? NetworkShared(),
|
network: userSpecifiedNetwork(vmDir: vmDir) ?? NetworkShared(),
|
||||||
additionalDiskAttachments: additionalDiskAttachments,
|
additionalDiskAttachments: additionalDiskAttachments,
|
||||||
directorySharingDevices: directoryShares() + rosettaDirectoryShare()
|
directorySharingDevices: directoryShares() + rosettaDirectoryShare(),
|
||||||
|
serialPorts: serialPorts
|
||||||
)
|
)
|
||||||
|
|
||||||
let vncImpl: VNC? = try {
|
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 {
|
func isInteractiveSession() -> Bool {
|
||||||
isatty(STDOUT_FILENO) == 1
|
isatty(STDOUT_FILENO) == 1
|
||||||
}
|
}
|
||||||
@@ -357,7 +397,13 @@ struct Run: AsyncParsableCommand {
|
|||||||
VMView(vm: vm!).onAppear {
|
VMView(vm: vm!).onAppear {
|
||||||
NSWindow.allowsAutomaticWindowTabbing = false
|
NSWindow.allowsAutomaticWindowTabbing = false
|
||||||
}.onDisappear {
|
}.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))
|
}.frame(width: CGFloat(vm!.config.display.width), height: CGFloat(vm!.config.display.height))
|
||||||
}.commands {
|
}.commands {
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ struct ARPCache {
|
|||||||
self.arpCommandOutput = arpCommandOutput
|
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)
|
let lines = String(decoding: arpCommandOutput, as: UTF8.self)
|
||||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
.components(separatedBy: "\n")
|
.components(separatedBy: "\n")
|
||||||
@@ -95,11 +95,6 @@ struct ARPCache {
|
|||||||
throw ARPCommandYieldedInvalidOutputError(explanation: "failed to parse MAC address \(rawMAC)")
|
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 {
|
if macAddress == mac {
|
||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ class Leases {
|
|||||||
return rawLeases
|
return rawLeases
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveMACAddress(macAddress: MACAddress) throws -> IPv4Address? {
|
func ResolveMACAddress(macAddress: MACAddress) throws -> IPv4Address? {
|
||||||
leases[macAddress]?.ip
|
leases[macAddress]?.ip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -40,7 +40,8 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
|||||||
init(vmDir: VMDirectory,
|
init(vmDir: VMDirectory,
|
||||||
network: Network = NetworkShared(),
|
network: Network = NetworkShared(),
|
||||||
additionalDiskAttachments: [VZDiskImageStorageDeviceAttachment] = [],
|
additionalDiskAttachments: [VZDiskImageStorageDeviceAttachment] = [],
|
||||||
directorySharingDevices: [VZDirectorySharingDeviceConfiguration] = []
|
directorySharingDevices: [VZDirectorySharingDeviceConfiguration] = [],
|
||||||
|
serialPorts: [VZSerialPortConfiguration] = []
|
||||||
) throws {
|
) throws {
|
||||||
name = vmDir.name
|
name = vmDir.name
|
||||||
config = try VMConfig.init(fromURL: vmDir.configURL)
|
config = try VMConfig.init(fromURL: vmDir.configURL)
|
||||||
@@ -54,7 +55,8 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
|||||||
let configuration = try Self.craftConfiguration(diskURL: vmDir.diskURL,
|
let configuration = try Self.craftConfiguration(diskURL: vmDir.diskURL,
|
||||||
nvramURL: vmDir.nvramURL, vmConfig: config,
|
nvramURL: vmDir.nvramURL, vmConfig: config,
|
||||||
network: network, additionalDiskAttachments: additionalDiskAttachments,
|
network: network, additionalDiskAttachments: additionalDiskAttachments,
|
||||||
directorySharingDevices: directorySharingDevices
|
directorySharingDevices: directorySharingDevices,
|
||||||
|
serialPorts: serialPorts
|
||||||
)
|
)
|
||||||
virtualMachine = VZVirtualMachine(configuration: configuration)
|
virtualMachine = VZVirtualMachine(configuration: configuration)
|
||||||
|
|
||||||
@@ -132,7 +134,8 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
|||||||
diskSizeGB: UInt16,
|
diskSizeGB: UInt16,
|
||||||
network: Network = NetworkShared(),
|
network: Network = NetworkShared(),
|
||||||
additionalDiskAttachments: [VZDiskImageStorageDeviceAttachment] = [],
|
additionalDiskAttachments: [VZDiskImageStorageDeviceAttachment] = [],
|
||||||
directorySharingDevices: [VZDirectorySharingDeviceConfiguration] = []
|
directorySharingDevices: [VZDirectorySharingDeviceConfiguration] = [],
|
||||||
|
serialPorts: [VZSerialPortConfiguration] = []
|
||||||
) async throws {
|
) async throws {
|
||||||
var ipswURL = ipswURL
|
var ipswURL = ipswURL
|
||||||
|
|
||||||
@@ -179,7 +182,8 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
|||||||
let configuration = try Self.craftConfiguration(diskURL: vmDir.diskURL, nvramURL: vmDir.nvramURL,
|
let configuration = try Self.craftConfiguration(diskURL: vmDir.diskURL, nvramURL: vmDir.nvramURL,
|
||||||
vmConfig: config, network: network,
|
vmConfig: config, network: network,
|
||||||
additionalDiskAttachments: additionalDiskAttachments,
|
additionalDiskAttachments: additionalDiskAttachments,
|
||||||
directorySharingDevices: directorySharingDevices
|
directorySharingDevices: directorySharingDevices,
|
||||||
|
serialPorts: serialPorts
|
||||||
)
|
)
|
||||||
virtualMachine = VZVirtualMachine(configuration: configuration)
|
virtualMachine = VZVirtualMachine(configuration: configuration)
|
||||||
|
|
||||||
@@ -261,7 +265,8 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
|||||||
vmConfig: VMConfig,
|
vmConfig: VMConfig,
|
||||||
network: Network = NetworkShared(),
|
network: Network = NetworkShared(),
|
||||||
additionalDiskAttachments: [VZDiskImageStorageDeviceAttachment],
|
additionalDiskAttachments: [VZDiskImageStorageDeviceAttachment],
|
||||||
directorySharingDevices: [VZDirectorySharingDeviceConfiguration]
|
directorySharingDevices: [VZDirectorySharingDeviceConfiguration],
|
||||||
|
serialPorts: [VZSerialPortConfiguration]
|
||||||
) throws -> VZVirtualMachineConfiguration {
|
) throws -> VZVirtualMachineConfiguration {
|
||||||
let configuration = VZVirtualMachineConfiguration()
|
let configuration = VZVirtualMachineConfiguration()
|
||||||
|
|
||||||
@@ -308,6 +313,9 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
|||||||
// Directory sharing devices
|
// Directory sharing devices
|
||||||
configuration.directorySharingDevices = directorySharingDevices
|
configuration.directorySharingDevices = directorySharingDevices
|
||||||
|
|
||||||
|
// Serial Port
|
||||||
|
configuration.serialPorts = serialPorts
|
||||||
|
|
||||||
try configuration.validate()
|
try configuration.validate()
|
||||||
|
|
||||||
return configuration
|
return configuration
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ extension Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum RuntimeError : Error {
|
enum RuntimeError : Error {
|
||||||
|
case VMConfigurationError(_ message: String)
|
||||||
case VMDoesNotExist(name: String)
|
case VMDoesNotExist(name: String)
|
||||||
case VMMissingFiles(_ message: String)
|
case VMMissingFiles(_ message: String)
|
||||||
case VMNotRunning(_ message: String)
|
case VMNotRunning(_ message: String)
|
||||||
@@ -65,6 +66,8 @@ protocol HasExitCode {
|
|||||||
extension RuntimeError : CustomStringConvertible {
|
extension RuntimeError : CustomStringConvertible {
|
||||||
public var description: String {
|
public var description: String {
|
||||||
switch self {
|
switch self {
|
||||||
|
case .VMConfigurationError(let message):
|
||||||
|
return message
|
||||||
case .VMDoesNotExist(let name):
|
case .VMDoesNotExist(let name):
|
||||||
return "the specified VM \"\(name)\" does not exist"
|
return "the specified VM \"\(name)\" does not exist"
|
||||||
case .VMMissingFiles(let message):
|
case .VMMissingFiles(let message):
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ final class MACAddressResolverTests: XCTestCase {
|
|||||||
""")
|
""")
|
||||||
|
|
||||||
XCTAssertEqual(IPv4Address("1.2.3.4"),
|
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 {
|
func testMultipleEntries() throws {
|
||||||
@@ -28,8 +28,8 @@ final class MACAddressResolverTests: XCTestCase {
|
|||||||
""")
|
""")
|
||||||
|
|
||||||
XCTAssertEqual(IPv4Address("1.2.3.4"),
|
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"),
|
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")!))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-3
@@ -3,9 +3,15 @@ hide:
|
|||||||
- navigation
|
- 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,
|
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
|
# Sponsorships
|
||||||
|
|
||||||
@@ -27,7 +33,7 @@ Our support team will answer it by replying to the issue or by updating the docu
|
|||||||
|
|
||||||
# Priority Support
|
# 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 |
|
| Severity | Support Impact | First Response Time SLA | Hours | How to Submit |
|
||||||
|----------|-----------------------------------------------------------------------------------------------|-------------------------|-------|--------------------------------------------------------------------------------------------------|
|
|----------|-----------------------------------------------------------------------------------------------|-------------------------|-------|--------------------------------------------------------------------------------------------------|
|
||||||
|
|||||||
Vendored
+3
-3
@@ -169,7 +169,7 @@
|
|||||||
<figcaption class="md-typeset">
|
<figcaption class="md-typeset">
|
||||||
<p>
|
<p>
|
||||||
Tart powers several continuous integration systems including
|
Tart powers several continuous integration systems including
|
||||||
<a href="/github-actions"
|
<a href="/integrations/github-actions"
|
||||||
>on‑demand GitHub Actions Runners</a
|
>on‑demand GitHub Actions Runners</a
|
||||||
>
|
>
|
||||||
and
|
and
|
||||||
@@ -261,7 +261,7 @@
|
|||||||
<hr/>
|
<hr/>
|
||||||
<cite>
|
<cite>
|
||||||
It’s been plain-sailing with the
|
It’s been plain-sailing with the
|
||||||
<a href="/github-actions">Cirrus Runners</a> —
|
<a href="/integrations/github-actions">Cirrus Runners</a> —
|
||||||
they’ve been great! They’re consistently 60+%
|
they’ve been great! They’re consistently 60+%
|
||||||
faster on workflows that we previously used Github
|
faster on workflows that we previously used Github
|
||||||
Actions’ macOS runners for.
|
Actions’ macOS runners for.
|
||||||
@@ -310,7 +310,7 @@
|
|||||||
<cite>
|
<cite>
|
||||||
Previously, we were using the GitHub‑hosted macOS runners
|
Previously, we were using the GitHub‑hosted macOS runners
|
||||||
and our iOS build took ~30 minutes. Now with
|
and our iOS build took ~30 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 minutes. That’s a huge boost to our productivity,
|
takes ~12 minutes. That’s a huge boost to our productivity,
|
||||||
and for only $150/month per runner it is much less expensive too.
|
and for only $150/month per runner it is much less expensive too.
|
||||||
</cite>
|
</cite>
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ extra_css:
|
|||||||
|
|
||||||
plugins:
|
plugins:
|
||||||
- blog
|
- blog
|
||||||
|
- rss:
|
||||||
|
match_path: blog/posts/.*
|
||||||
|
date_from_meta:
|
||||||
|
as_creation: date
|
||||||
- social
|
- social
|
||||||
- search
|
- search
|
||||||
- minify
|
- minify
|
||||||
|
|||||||
Reference in New Issue
Block a user