Compare commits

...

27 Commits

Author SHA1 Message Date
Jonathan Nobels
79ef5b32fe natlab: fix unixgram plumbing and add mac virtualization
Tweaked the unixgram plumbing to use the vm's remote
unix address for write operations.

Rudimentary macOS virtualization added which will spin
up a VM with some hardcoded config options that work with
natlabd's default settings in -dgram mode.

Signed-off-by: Jonathan Nobels <jonathan@tailscale.com>
2024-08-02 12:00:41 -04:00
Brad Fitzpatrick
e6336fad18 add dgram mode for Hypervisor.Framework and VZFileHandleNetworkDeviceAttachment
Change-Id: I476b48fdf500699093f01ce353f9089ab3a9c08d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-07-31 14:42:35 -07:00
Brad Fitzpatrick
e971923a92 name NAT types, add constructors
Change-Id: Id558e763773e6efa700cfb7943b64c78bfffc4ed
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-07-29 19:07:34 -07:00
Brad Fitzpatrick
0e9bbbebeb flesh out docs on WriteUDPPacketNoNAT
Change-Id: Id029726d22fe084d15b75510e37c2fcb453fd97d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-07-29 19:07:34 -07:00
Brad Fitzpatrick
ae484da02e start to add NAT-PMP port mapping
Change-Id: Id8061ab8e907a9473f0931deebd6cabcd32deffc
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-07-29 19:07:34 -07:00
Brad Fitzpatrick
40814eafb4 add gokrazy start script w/ qemu-system-x86_64 microvm
Change-Id: I85c4d155c63b03fea84976c7a906fcaf93f7fc53
2024-07-29 19:07:34 -07:00
Brad Fitzpatrick
ba5ada52ad add flag to demo hard-vs-easy quickly
Change-Id: I48945c0b84e7e863b71dd6bdf97cd41b3b1e81bb
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-07-29 19:07:34 -07:00
Brad Fitzpatrick
38aa2687ac make hard NAT work
Change-Id: Id7b4195639c7fbff17c5b52e3780a1fdbcefbd31
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-07-29 19:07:34 -07:00
Brad Fitzpatrick
92c3a4ce9a add start of Easy NAT impl
untested so far

Change-Id: Id5a265693d77a4552342c08e643e4a7ed5545e6a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-07-29 19:07:34 -07:00
Brad Fitzpatrick
c188795156 start to write down some TODOs in case I forget
Change-Id: I1cebc9c11e63892e0fe829e3f2949696b97955ac
2024-07-29 19:07:34 -07:00
Brad Fitzpatrick
cda2300682 add start of Hard NAT impl
untested so far

Change-Id: I682b604d0e90debf9eae3f1814663f336d03f57c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-07-29 19:07:34 -07:00
Brad Fitzpatrick
bebfa254cf change, document NATTable locking rules
Change-Id: I4f01343911102f5acf16ca6d5eb39ec98a5f2478
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-07-29 19:07:34 -07:00
Brad Fitzpatrick
0f2ecf8a18 start of pluggable NAT impl types
Change-Id: I633bce41e978f385eab26478baa42e56178c489a
2024-07-29 19:07:34 -07:00
Brad Fitzpatrick
1071dc5d4d add a second network
Change-Id: I0337e2f7c2402384c00ff81e7ec7614168c4fe5b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-07-29 19:07:34 -07:00
Brad Fitzpatrick
95da5023fc support proxying to real DERPs, for testing
Change-Id: I27da972ed6c37188b2de17e3d9287410eace73e4
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-07-29 19:07:34 -07:00
Brad Fitzpatrick
3ebc3495e7 add UDPPacket, NAT, routing abstractions, move/redo STUN
It's starting to come alive!

Change-Id: Iffa853eaab6da001d5c0935e45d3b47baf7211a6
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-07-29 19:07:34 -07:00
Brad Fitzpatrick
e617cf5af8 start to handle L2 vs L3 properly
Change-Id: I0efc9320aff419dc4b3e97c623c9af7f9d5dd48a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-07-29 19:07:33 -07:00
Brad Fitzpatrick
c860129eb0 add EthernetPacket, network.HandleEthernetPacket
Change-Id: I9b9f471c8fcc49ea16d732fe3ce06d5be4ff1125
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-07-29 19:07:33 -07:00
Brad Fitzpatrick
013ea64e94 move more to network, hardcode less
Change-Id: If1c773153f7f3fa7ea483d1b7231193ab093278a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-07-29 19:07:33 -07:00
Brad Fitzpatrick
87f777d21b start on declarative world config, not hard coding things
Change-Id: I76afccc50311f0894fbda4064ff0dd9696ef820e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-07-29 19:07:33 -07:00
Brad Fitzpatrick
9f63076827 STUN
Change-Id: I77eacb007d7fd315d165f14be2d2ecd892900843
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-07-29 19:07:33 -07:00
Brad Fitzpatrick
8070c8d1d5 proxy controlplane so netcheck can get derpmap
Change-Id: Iecef62dc6f3904f588597e3134429b1521e9fe10
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-07-29 19:07:33 -07:00
Brad Fitzpatrick
c0581779ed start of TCP netstack interception
Change-Id: I5523f8d45e34ac8074a55a67b3d3fd978bd7de44
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-07-29 19:07:33 -07:00
Brad Fitzpatrick
13110cc11e DNS
Change-Id: Icabbdbf4810e2b4ac5ebe17550c8fdb344ebaaa4
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-07-29 19:07:33 -07:00
Brad Fitzpatrick
4c4d8cb4b8 DHCP and ARP
Change-Id: Id0a8bfd7047253562cd800ff4e3c75154fa773a8
2024-07-29 19:07:33 -07:00
Brad Fitzpatrick
7abee8aee6 packets
Change-Id: If385bb5f88ff7db9e388bd0724a1f953a3b11639
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-07-29 19:07:33 -07:00
Brad Fitzpatrick
58fe931bca vnet: WIP
Change-Id: Id26b5ea0a1ec21dcc18e01aa4937351cfa49cd7c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-07-29 19:07:33 -07:00
31 changed files with 3797 additions and 0 deletions

4
.gitignore vendored
View File

@@ -43,3 +43,7 @@ client/web/build/assets
/gocross
/dist
# Ignore xcode userstate and workspace data
*.xcuserstate
*.xcworkspacedata

1
go.mod
View File

@@ -131,6 +131,7 @@ require (
github.com/gobuffalo/flect v1.0.2 // indirect
github.com/goccy/go-yaml v1.12.0 // indirect
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect

2
go.sum
View File

@@ -477,6 +477,8 @@ github.com/google/go-containerregistry v0.18.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2 h1:CVuJwN34x4xM2aT4sIKhmeib40NeBPhRihNjQmpJsA4=
github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=

View File

@@ -1,3 +1,5 @@
# ./qemu-wrapper /tmp/qemu.sock qemu-system-x86_64 [...] -netdev socket,id=vlan,fd=3 -device virtio-net-pci,netdev=vlan
help:
echo "See Makefile"
@@ -6,3 +8,6 @@ image:
qemu: image
qemu-system-x86_64 -m 1G -drive file=tsapp.img,format=raw -boot d -netdev user,id=user.0 -device virtio-net-pci,netdev=user.0 -serial mon:stdio -audio none
qemuwrap: image
go run ./cmd/vnetwrap qemu-system-x86_64 -m 1G -drive file=tsapp.img,format=raw -boot d -netdev socket,fd=3,id=user.0 -device virtio-net-pci,netdev=user.0 -serial mon:stdio -audio none

View File

@@ -0,0 +1,68 @@
package main
import (
"encoding/binary"
"io"
"log"
"net"
"os"
"os/exec"
)
const path = "/tmp/vnet.sock"
func serve(ln net.Listener) {
for {
c, err := ln.Accept()
if err != nil {
log.Printf("Accept: %v", err)
continue
}
go serveConn(c)
}
}
func serveConn(c net.Conn) {
log.Printf("Got conn")
defer c.Close()
buf := make([]byte, 4<<10)
for {
if _, err := io.ReadFull(c, buf[:4]); err != nil {
log.Printf("ReadFull header: %v", err)
return
}
n := binary.BigEndian.Uint32(buf[:4])
if _, err := io.ReadFull(c, buf[:n]); err != nil {
log.Printf("ReadFull pkt: %v", err)
return
}
log.Printf("pkt %d bytes: % 02x", n, buf[:n])
}
}
func main() {
srv, err := net.Listen("unix", path)
if err != nil {
log.Fatal(err)
}
go serve(srv)
conn, err := net.Dial("unix", path)
if err != nil {
log.Fatal(err)
}
fd, err := conn.(*net.UnixConn).File()
if err != nil {
log.Fatal(err)
}
cmd := exec.Command(os.Args[1], os.Args[2:]...) // #nosec G204
cmd.ExtraFiles = append(cmd.ExtraFiles, fd)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
}

229
natlab/natlabd/nat.go Normal file
View File

@@ -0,0 +1,229 @@
package main
import (
"errors"
"math/rand/v2"
"net/netip"
"time"
"tailscale.com/util/mak"
)
// IPPool is the interface that a NAT implementation uses to get information
// about a network.
//
// Outside of tests, this is typically a *network.
type IPPool interface {
// WANIP returns the primary WAN IP address.
//
// TODO: add another method for networks with multiple WAN IP addresses.
WANIP() netip.Addr
// SoleLanIP reports whether this network has a sole LAN client
// and if so, its IP address.
SoleLANIP() (_ netip.Addr, ok bool)
// TODO: port availability stuff for interacting with portmapping
}
// newTableFunc is a constructor for a NAT table.
// The provided IPPool is typically (outside of tests) a *network.
type newTableFunc func(IPPool) (NATTable, error)
// natTypes are the known NAT types.
var natTypes = map[string]newTableFunc{}
// registerNATType registers a NAT type.
func registerNATType(name string, f newTableFunc) {
if _, ok := natTypes[name]; ok {
panic("duplicate NAT type: " + name)
}
natTypes[name] = f
}
// NATTable is what a NAT implementation is expected to do.
//
// This project tests Tailscale as it faces various combinations various NAT
// implementations (e.g. Linux easy style NAT vs FreeBSD hard/endpoint dependent
// NAT vs Cloud 1:1 NAT, etc)
//
// Implementations of NATTable need not handle concurrency; the natlab serializes
// all calls into a NATTable.
//
// The provided `at` value will typically be time.Now, except for tests.
// Implementations should not use real time and should only compare
// previously provided time values.
type NATTable interface {
// PickOutgoingSrc returns the source address to use for an outgoing packet.
//
// The result should either be invalid (to drop the packet) or a WAN (not
// private) IP address.
//
// Typically, the src is a LAN source IP address, but it might also be a WAN
// IP address if the packet is being forwarded for a source machine that has
// a public IP address.
PickOutgoingSrc(src, dst netip.AddrPort, at time.Time) (wanSrc netip.AddrPort)
// PickIncomingDst returns the destination address to use for an incoming
// packet. The incoming src address is always a public WAN IP.
//
// The result should either be invalid (to drop the packet) or the IP
// address of a machine on the local network address, usually a private
// LAN IP.
PickIncomingDst(src, dst netip.AddrPort, at time.Time) (lanDst netip.AddrPort)
}
// oneToOneNAT is a 1:1 NAT, like a typical EC2 VM.
type oneToOneNAT struct {
lanIP netip.Addr
wanIP netip.Addr
}
func init() {
registerNATType("one2one", func(p IPPool) (NATTable, error) {
lanIP, ok := p.SoleLANIP()
if !ok {
return nil, errors.New("can't use one2one NAT type on networks other than single-node networks")
}
return &oneToOneNAT{lanIP: lanIP, wanIP: p.WANIP()}, nil
})
}
func (n *oneToOneNAT) PickOutgoingSrc(src, dst netip.AddrPort, at time.Time) (wanSrc netip.AddrPort) {
return netip.AddrPortFrom(n.wanIP, src.Port())
}
func (n *oneToOneNAT) PickIncomingDst(src, dst netip.AddrPort, at time.Time) (lanDst netip.AddrPort) {
return netip.AddrPortFrom(n.lanIP, dst.Port())
}
type hardKeyOut struct {
lanIP netip.Addr
dst netip.AddrPort
}
type hardKeyIn struct {
wanPort uint16
src netip.AddrPort
}
type portMappingAndTime struct {
port uint16
at time.Time
}
type lanAddrAndTime struct {
lanAddr netip.AddrPort
at time.Time
}
// hardNAT is an "Endpoint Dependent" NAT, like FreeBSD/pfSense/OPNsense.
// This is shown as "MappingVariesByDestIP: true" by netcheck, and what
// Tailscale calls "Hard NAT".
type hardNAT struct {
wanIP netip.Addr
out map[hardKeyOut]portMappingAndTime
in map[hardKeyIn]lanAddrAndTime
}
func init() {
registerNATType("hard", func(p IPPool) (NATTable, error) {
return &hardNAT{wanIP: p.WANIP()}, nil
})
}
func (n *hardNAT) PickOutgoingSrc(src, dst netip.AddrPort, at time.Time) (wanSrc netip.AddrPort) {
ko := hardKeyOut{src.Addr(), dst}
if pm, ok := n.out[ko]; ok {
// Existing flow.
// TODO: bump timestamp
return netip.AddrPortFrom(n.wanIP, pm.port)
}
// No existing mapping exists. Create one.
// TODO: clean up old expired mappings
// Instead of proper data structures that would be efficient, we instead
// just loop a bunch and look for a free port. This project is only used
// by tests and doesn't care about performance, this is good enough.
for {
port := rand.N(uint16(32<<10)) + 32<<10 // pick some "ephemeral" port
ki := hardKeyIn{wanPort: port, src: dst}
if _, ok := n.in[ki]; ok {
// Port already in use.
continue
}
mak.Set(&n.in, ki, lanAddrAndTime{lanAddr: src, at: at})
mak.Set(&n.out, ko, portMappingAndTime{port: port, at: at})
return netip.AddrPortFrom(n.wanIP, port)
}
}
func (n *hardNAT) PickIncomingDst(src, dst netip.AddrPort, at time.Time) (lanDst netip.AddrPort) {
if dst.Addr() != n.wanIP {
return netip.AddrPort{} // drop; not for us. shouldn't happen if natlabd routing isn't broken.
}
ki := hardKeyIn{wanPort: dst.Port(), src: src}
if pm, ok := n.in[ki]; ok {
// Existing flow.
return pm.lanAddr
}
return netip.AddrPort{} // drop; no mapping
}
type easyKeyOut struct {
src netip.AddrPort
}
// easyNAT is an "Endpoint Independent" NAT, like Linux and most home routers
// (many of which are Linux).
//
// This is shown as "MappingVariesByDestIP: false" by netcheck, and what
// Tailscale calls "Easy NAT".
//
// Unlike Linux, this implementation is capped at 32k entries and doesn't resort
// to other allocation strategies when all 32k WAN ports are taken.
type easyNAT struct {
wanIP netip.Addr
out map[netip.AddrPort]portMappingAndTime
in map[uint16]lanAddrAndTime
}
func init() {
registerNATType("easy", func(p IPPool) (NATTable, error) {
return &easyNAT{wanIP: p.WANIP()}, nil
})
}
func (n *easyNAT) PickOutgoingSrc(src, dst netip.AddrPort, at time.Time) (wanSrc netip.AddrPort) {
if pm, ok := n.out[src]; ok {
// Existing flow.
// TODO: bump timestamp
return netip.AddrPortFrom(n.wanIP, pm.port)
}
// Loop through all 32k high (ephemeral) ports, starting at a random
// position and looping back around to the start.
start := rand.N(uint16(32 << 10))
for off := range uint16(32 << 10) {
port := 32<<10 + (start+off)%(32<<10)
if _, ok := n.in[port]; !ok {
wanAddr := netip.AddrPortFrom(n.wanIP, port)
// Found a free port.
mak.Set(&n.out, src, portMappingAndTime{port: port, at: at})
mak.Set(&n.in, port, lanAddrAndTime{lanAddr: src, at: at})
return wanAddr
}
}
return netip.AddrPort{} // failed to allocate a mapping; TODO: fire an alert?
}
func (n *easyNAT) PickIncomingDst(src, dst netip.AddrPort, at time.Time) (lanDst netip.AddrPort) {
if dst.Addr() != n.wanIP {
return netip.AddrPort{} // drop; not for us. shouldn't happen if natlabd routing isn't broken.
}
return n.in[dst.Port()].lanAddr
}

1298
natlab/natlabd/natlabd.go Normal file

File diff suppressed because it is too large Load Diff

20
natlab/run-krazy.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/usr/bin/env bash
echo "Type 'C-a c' to enter monitor; q to quit."
set -eux
qemu-system-x86_64 -M microvm,isa-serial=off \
-m 1G \
-nodefaults -no-user-config -nographic \
-kernel $HOME/src/github.com/tailscale/gokrazy-kernel/vmlinuz \
-append "console=hvc0 root=PARTUUID=60c24cc1-f3f9-427a-8199-dd02023b0001/PARTNROFF=1 ro init=/gokrazy/init panic=10 oops=panic pci=off nousb tsc=unstable clocksource=hpet" \
-drive id=blk0,file=$HOME/src/tailscale.com/gokrazy/tsapp.img,format=raw \
-device virtio-blk-device,drive=blk0 \
-netdev stream,id=net0,addr.type=unix,addr.path=/tmp/qemu.sock \
-device virtio-serial-device \
-device virtio-net-device,netdev=net0,mac=5a:94:ef:e4:0c:ee \
-chardev stdio,id=virtiocon0,mux=on \
-device virtconsole,chardev=virtiocon0 \
-mon chardev=virtiocon0,mode=readline \
-audio none

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.virtualization</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
Copyright © 2023 Apple Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,20 @@
import Foundation
// We need to make this all configurable via config file of some kind and
// read it in.
struct Config {
static let mac = "5a:94:ef:e4:0c:ee"
static let serverSocket = "/tmp/qemu.sock"
static let clientSocket = "/tmp/qemu_client.sock"
static let memorySize = (4 * 1024 * 1024 * 1024) as UInt64
}
let vmBundlePath = NSHomeDirectory() + "/VM.bundle/"
let vmBundleURL = URL(fileURLWithPath: vmBundlePath)
let auxiliaryStorageURL = vmBundleURL.appendingPathComponent("AuxiliaryStorage")
let diskImageURL = vmBundleURL.appendingPathComponent("Disk.img")
let hardwareModelURL = vmBundleURL.appendingPathComponent("HardwareModel")
let machineIdentifierURL = vmBundleURL.appendingPathComponent("MachineIdentifier")
let restoreImageURL = vmBundleURL.appendingPathComponent("RestoreImage.ipsw")
let saveFileURL = vmBundleURL.appendingPathComponent("SaveFile.vzvmsave")

View File

@@ -0,0 +1,21 @@
/*
See the LICENSE.txt file for this samples licensing information.
Abstract:
A class that conforms to `VZVirtualMachineDelegate` and tracks the virtual machine's state.
*/
import Foundation
import Virtualization
class VnetDelegate: NSObject, VZVirtualMachineDelegate {
func virtualMachine(_ virtualMachine: VZVirtualMachine, didStopWithError error: Error) {
NSLog("Virtual machine did stop with error: \(error.localizedDescription)")
exit(-1)
}
func guestDidStop(_ virtualMachine: VZVirtualMachine) {
NSLog("Guest did stop virtual machine.")
exit(0)
}
}

View File

@@ -0,0 +1,126 @@
/*
See the LICENSE.txt file for this samples licensing information.
Abstract:
The helper that creates various configuration objects exposed in the `VZVirtualMachineConfiguration`.
*/
import Foundation
import Virtualization
#if arch(arm64)
struct VnetHostConfigHelper {
static func computeCPUCount() -> Int {
let totalAvailableCPUs = ProcessInfo.processInfo.processorCount
var virtualCPUCount = totalAvailableCPUs <= 1 ? 1 : totalAvailableCPUs - 1
virtualCPUCount = max(virtualCPUCount, VZVirtualMachineConfiguration.minimumAllowedCPUCount)
virtualCPUCount = min(virtualCPUCount, VZVirtualMachineConfiguration.maximumAllowedCPUCount)
return virtualCPUCount
}
static func computeMemorySize() -> UInt64 {
// Set the amount of system memory to 4 GB; this is a baseline value
// that you can change depending on your use case.
var memorySize = Config.memorySize
memorySize = max(memorySize, VZVirtualMachineConfiguration.minimumAllowedMemorySize)
memorySize = min(memorySize, VZVirtualMachineConfiguration.maximumAllowedMemorySize)
return memorySize
}
static func createBootLoader() -> VZMacOSBootLoader {
return VZMacOSBootLoader()
}
static func createGraphicsDeviceConfiguration() -> VZMacGraphicsDeviceConfiguration {
let graphicsConfiguration = VZMacGraphicsDeviceConfiguration()
graphicsConfiguration.displays = [
// The system arbitrarily chooses the resolution of the display to be 1920 x 1200.
VZMacGraphicsDisplayConfiguration(widthInPixels: 1920, heightInPixels: 1200, pixelsPerInch: 80)
]
return graphicsConfiguration
}
static func createBlockDeviceConfiguration() -> VZVirtioBlockDeviceConfiguration {
guard let diskImageAttachment = try? VZDiskImageStorageDeviceAttachment(url: diskImageURL, readOnly: false) else {
fatalError("Failed to create Disk image.")
}
let disk = VZVirtioBlockDeviceConfiguration(attachment: diskImageAttachment)
return disk
}
static func createNetworkDeviceConfiguration() -> VZVirtioNetworkDeviceConfiguration {
let networkDevice = VZVirtioNetworkDeviceConfiguration()
networkDevice.macAddress = VZMACAddress(string: Config.mac)!
let socket = Darwin.socket(AF_UNIX, SOCK_DGRAM, 0)
let serverSocket = Config.serverSocket
let clientSocket = Config.clientSocket
unlink(clientSocket)
var clientAddr = sockaddr_un()
clientAddr.sun_family = sa_family_t(AF_UNIX)
clientSocket.withCString { ptr in
withUnsafeMutablePointer(to: &clientAddr.sun_path.0) { dest in
_ = strcpy(dest, ptr)
}
}
let bindRes = Darwin.bind(socket,
withUnsafePointer(to: &clientAddr, { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { $0 } }),
socklen_t(MemoryLayout<sockaddr_un>.size))
if bindRes == -1 {
print("Error binding virtual network client socket - \(String(cString: strerror(errno)))")
return networkDevice
}
var serverAddr = sockaddr_un()
serverAddr.sun_family = sa_family_t(AF_UNIX)
serverSocket.withCString { ptr in
withUnsafeMutablePointer(to: &serverAddr.sun_path.0) { dest in
_ = strcpy(dest, ptr)
}
}
let connectRes = Darwin.connect(socket,
withUnsafePointer(to: &serverAddr, { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { $0 } }),
socklen_t(MemoryLayout<sockaddr_un>.size))
if connectRes == -1 {
print("Error binding virtual network server socket - \(String(cString: strerror(errno)))")
return networkDevice
}
print("Virtual if mac address is \(Config.mac)")
print("Client bound to \(clientSocket)")
print("Connected to server at \(serverSocket)")
print("Socket fd is \(socket)")
let handle = FileHandle(fileDescriptor: socket)
let device = VZFileHandleNetworkDeviceAttachment(fileHandle: handle)
networkDevice.attachment = device
return networkDevice
}
static func createPointingDeviceConfiguration() -> VZPointingDeviceConfiguration {
return VZMacTrackpadConfiguration()
}
static func createKeyboardConfiguration() -> VZKeyboardConfiguration {
if #available(macOS 14.0, *) {
return VZMacKeyboardConfiguration()
} else {
return VZUSBKeyboardConfiguration()
}
}
}
#endif

View File

@@ -0,0 +1,53 @@
/*
See the LICENSE.txt file for this samples licensing information.
Abstract:
Download the latest macOS restore image from the network.
*/
import Foundation
import Virtualization
#if arch(arm64)
class MacOSRestoreImage: NSObject {
private var downloadObserver: NSKeyValueObservation?
// MARK: Observe the download progress.
public func download(completionHandler: @escaping () -> Void) {
NSLog("Attempting to download latest available restore image.")
VZMacOSRestoreImage.fetchLatestSupported { [self](result: Result<VZMacOSRestoreImage, Error>) in
switch result {
case let .failure(error):
fatalError(error.localizedDescription)
case let .success(restoreImage):
downloadRestoreImage(restoreImage: restoreImage, completionHandler: completionHandler)
}
}
}
// MARK: Download the restore image from the network.
private func downloadRestoreImage(restoreImage: VZMacOSRestoreImage, completionHandler: @escaping () -> Void) {
let downloadTask = URLSession.shared.downloadTask(with: restoreImage.url) { localURL, response, error in
if let error = error {
fatalError("Download failed. \(error.localizedDescription).")
}
guard (try? FileManager.default.moveItem(at: localURL!, to: restoreImageURL)) != nil else {
fatalError("Failed to move downloaded restore image to \(restoreImageURL).")
}
completionHandler()
}
downloadObserver = downloadTask.progress.observe(\.fractionCompleted, options: [.initial, .new]) { (progress, change) in
NSLog("Restore image download progress: \(change.newValue! * 100).")
}
downloadTask.resume()
}
}
#endif

View File

@@ -0,0 +1,169 @@
/*
See the LICENSE.txt file for this samples licensing information.
Abstract:
A helper class to install a macOS virtual machine.
*/
import Virtualization
#if arch(arm64)
class MacOSVirtualMachineInstaller: NSObject {
private var installationObserver: NSKeyValueObservation?
private var virtualMachine: VZVirtualMachine!
private var virtualMachineResponder: VnetDelegate?
// Create a bundle on the user's Home directory to store any artifacts
// that the installation produces.
public func setUpVirtualMachineArtifacts() {
createVMBundle()
}
// MARK: Install macOS onto the virtual machine from IPSW.
public func installMacOS(ipswURL: URL) {
NSLog("Attempting to install from IPSW at \(ipswURL).")
VZMacOSRestoreImage.load(from: ipswURL, completionHandler: { [self](result: Result<VZMacOSRestoreImage, Error>) in
switch result {
case let .failure(error):
fatalError(error.localizedDescription)
case let .success(restoreImage):
installMacOS(restoreImage: restoreImage)
}
})
}
// MARK: - Internal helper functions.
private func installMacOS(restoreImage: VZMacOSRestoreImage) {
guard let macOSConfiguration = restoreImage.mostFeaturefulSupportedConfiguration else {
fatalError("No supported configuration available.")
}
if !macOSConfiguration.hardwareModel.isSupported {
fatalError("macOSConfiguration configuration isn't supported on the current host.")
}
DispatchQueue.main.async { [self] in
setupVirtualMachine(macOSConfiguration: macOSConfiguration)
startInstallation(restoreImageURL: restoreImage.url)
}
}
// MARK: Create the Mac platform configuration.
private func createMacPlatformConfiguration(macOSConfiguration: VZMacOSConfigurationRequirements) -> VZMacPlatformConfiguration {
let macPlatformConfiguration = VZMacPlatformConfiguration()
guard let auxiliaryStorage = try? VZMacAuxiliaryStorage(creatingStorageAt: auxiliaryStorageURL,
hardwareModel: macOSConfiguration.hardwareModel,
options: []) else {
fatalError("Failed to create auxiliary storage.")
}
macPlatformConfiguration.auxiliaryStorage = auxiliaryStorage
macPlatformConfiguration.hardwareModel = macOSConfiguration.hardwareModel
macPlatformConfiguration.machineIdentifier = VZMacMachineIdentifier()
// Store the hardware model and machine identifier to disk so that you
// can retrieve them for subsequent boots.
try! macPlatformConfiguration.hardwareModel.dataRepresentation.write(to: hardwareModelURL)
try! macPlatformConfiguration.machineIdentifier.dataRepresentation.write(to: machineIdentifierURL)
return macPlatformConfiguration
}
// MARK: Create the virtual machine configuration and instantiate the virtual machine.
private func setupVirtualMachine(macOSConfiguration: VZMacOSConfigurationRequirements) {
let virtualMachineConfiguration = VZVirtualMachineConfiguration()
virtualMachineConfiguration.platform = createMacPlatformConfiguration(macOSConfiguration: macOSConfiguration)
virtualMachineConfiguration.cpuCount = VnetHostConfigHelper.computeCPUCount()
if virtualMachineConfiguration.cpuCount < macOSConfiguration.minimumSupportedCPUCount {
fatalError("CPUCount isn't supported by the macOS configuration.")
}
virtualMachineConfiguration.memorySize = VnetHostConfigHelper.computeMemorySize()
if virtualMachineConfiguration.memorySize < macOSConfiguration.minimumSupportedMemorySize {
fatalError("memorySize isn't supported by the macOS configuration.")
}
// Create a 128 GB disk image.
createDiskImage()
virtualMachineConfiguration.bootLoader = VnetHostConfigHelper.createBootLoader()
virtualMachineConfiguration.graphicsDevices = [VnetHostConfigHelper.createGraphicsDeviceConfiguration()]
virtualMachineConfiguration.storageDevices = [VnetHostConfigHelper.createBlockDeviceConfiguration()]
virtualMachineConfiguration.networkDevices = [VnetHostConfigHelper.createNetworkDeviceConfiguration()]
virtualMachineConfiguration.pointingDevices = [VnetHostConfigHelper.createPointingDeviceConfiguration()]
virtualMachineConfiguration.keyboards = [VnetHostConfigHelper.createKeyboardConfiguration()]
try! virtualMachineConfiguration.validate()
if #available(macOS 14.0, *) {
try! virtualMachineConfiguration.validateSaveRestoreSupport()
}
virtualMachine = VZVirtualMachine(configuration: virtualMachineConfiguration)
virtualMachineResponder = VnetDelegate()
virtualMachine.delegate = virtualMachineResponder
}
// MARK: Begin macOS installation.
private func startInstallation(restoreImageURL: URL) {
let installer = VZMacOSInstaller(virtualMachine: virtualMachine, restoringFromImageAt: restoreImageURL)
NSLog("Starting installation.")
installer.install(completionHandler: { (result: Result<Void, Error>) in
if case let .failure(error) = result {
fatalError(error.localizedDescription)
} else {
NSLog("Installation succeeded.")
}
})
// Observe installation progress.
installationObserver = installer.progress.observe(\.fractionCompleted, options: [.initial, .new]) { (progress, change) in
NSLog("Installation progress: \(change.newValue! * 100).")
}
}
private func createVMBundle() {
let bundleFd = mkdir(vmBundlePath, S_IRWXU | S_IRWXG | S_IRWXO)
if bundleFd == -1 {
if errno == EEXIST {
fatalError("Failed to create VM.bundle: the base directory already exists.")
}
fatalError("Failed to create VM.bundle.")
}
let result = close(bundleFd)
if result != 0 {
fatalError("Failed to close VM.bundle.")
}
}
// Create an empty disk image for the virtual machine.
private func createDiskImage() {
let diskFd = open(diskImageURL.path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR)
if diskFd == -1 {
fatalError("Cannot create disk image.")
}
// 128 GB disk space.
var result = ftruncate(diskFd, 128 * 1024 * 1024 * 1024)
if result != 0 {
fatalError("ftruncate() failed.")
}
result = close(diskFd)
if result != 0 {
fatalError("Failed to close the disk image.")
}
}
}
#endif

View File

@@ -0,0 +1,44 @@
/*
See the LICENSE.txt file for this samples licensing information.
Abstract:
The entry for `InstallationTool`.
*/
import Foundation
#if arch(arm64)
let installer = MacOSVirtualMachineInstaller()
if CommandLine.arguments.count == 2 {
let ipswPath = String(CommandLine.arguments[1])
let ipswURL = URL(fileURLWithPath: ipswPath)
guard ipswURL.isFileURL else {
fatalError("The provided IPSW path is not a valid file URL.")
}
installer.setUpVirtualMachineArtifacts()
installer.installMacOS(ipswURL: ipswURL)
dispatchMain()
} else if CommandLine.arguments.count == 1 {
installer.setUpVirtualMachineArtifacts()
let restoreImage = MacOSRestoreImage()
restoreImage.download {
// Install from the restore image that you downloaded.
installer.installMacOS(ipswURL: restoreImageURL)
}
dispatchMain()
} else {
NSLog("Invalid argument. Please either provide the path to an IPSW file, or run this tool without any argument.")
exit(-1)
}
#else
NSLog("This tool can only be run on Apple Silicon Macs.")
#endif

View File

@@ -0,0 +1,196 @@
/*
See the LICENSE.txt file for this samples licensing information.
Abstract:
The app delegate that sets up and starts the virtual machine.
*/
import Cocoa
import Foundation
import Virtualization
@main
class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet var window: NSWindow!
@IBOutlet weak var virtualMachineView: VZVirtualMachineView!
private var virtualMachineResponder: VnetDelegate?
private var virtualMachine: VZVirtualMachine!
// MARK: Create the Mac platform configuration.
#if arch(arm64)
private func createMacPlaform() -> VZMacPlatformConfiguration {
let macPlatform = VZMacPlatformConfiguration()
let auxiliaryStorage = VZMacAuxiliaryStorage(contentsOf: auxiliaryStorageURL)
macPlatform.auxiliaryStorage = auxiliaryStorage
if !FileManager.default.fileExists(atPath: vmBundlePath) {
fatalError("Missing Virtual Machine Bundle at \(vmBundlePath). Run InstallationTool first to create it.")
}
// Retrieve the hardware model and save this value to disk
// during installation.
guard let hardwareModelData = try? Data(contentsOf: hardwareModelURL) else {
fatalError("Failed to retrieve hardware model data.")
}
guard let hardwareModel = VZMacHardwareModel(dataRepresentation: hardwareModelData) else {
fatalError("Failed to create hardware model.")
}
if !hardwareModel.isSupported {
fatalError("The hardware model isn't supported on the current host")
}
macPlatform.hardwareModel = hardwareModel
// Retrieve the machine identifier and save this value to disk
// during installation.
guard let machineIdentifierData = try? Data(contentsOf: machineIdentifierURL) else {
fatalError("Failed to retrieve machine identifier data.")
}
guard let machineIdentifier = VZMacMachineIdentifier(dataRepresentation: machineIdentifierData) else {
fatalError("Failed to create machine identifier.")
}
macPlatform.machineIdentifier = machineIdentifier
return macPlatform
}
// MARK: Create the virtual machine configuration and instantiate the virtual machine.
private func createVirtualMachine() {
let virtualMachineConfiguration = VZVirtualMachineConfiguration()
virtualMachineConfiguration.platform = createMacPlaform()
virtualMachineConfiguration.bootLoader = VnetHostConfigHelper.createBootLoader()
virtualMachineConfiguration.cpuCount = VnetHostConfigHelper.computeCPUCount()
virtualMachineConfiguration.memorySize = VnetHostConfigHelper.computeMemorySize()
virtualMachineConfiguration.graphicsDevices = [VnetHostConfigHelper.createGraphicsDeviceConfiguration()]
virtualMachineConfiguration.storageDevices = [VnetHostConfigHelper.createBlockDeviceConfiguration()]
virtualMachineConfiguration.networkDevices = [VnetHostConfigHelper.createNetworkDeviceConfiguration()]
virtualMachineConfiguration.pointingDevices = [VnetHostConfigHelper.createPointingDeviceConfiguration()]
virtualMachineConfiguration.keyboards = [VnetHostConfigHelper.createKeyboardConfiguration()]
try! virtualMachineConfiguration.validate()
if #available(macOS 14.0, *) {
try! virtualMachineConfiguration.validateSaveRestoreSupport()
}
virtualMachine = VZVirtualMachine(configuration: virtualMachineConfiguration)
}
// MARK: Start or restore the virtual machine.
func startVirtualMachine() {
virtualMachine.start(completionHandler: { (result) in
if case let .failure(error) = result {
fatalError("Virtual machine failed to start with \(error)")
}
})
}
func resumeVirtualMachine() {
virtualMachine.resume(completionHandler: { (result) in
if case let .failure(error) = result {
fatalError("Virtual machine failed to resume with \(error)")
}
})
}
@available(macOS 14.0, *)
func restoreVirtualMachine() {
virtualMachine.restoreMachineStateFrom(url: saveFileURL, completionHandler: { [self] (error) in
// Remove the saved file. Whether success or failure, the state no longer matches the VM's disk.
let fileManager = FileManager.default
try! fileManager.removeItem(at: saveFileURL)
if error == nil {
self.resumeVirtualMachine()
} else {
self.startVirtualMachine()
}
})
}
#endif
func applicationDidFinishLaunching(_ aNotification: Notification) {
#if arch(arm64)
DispatchQueue.main.async { [self] in
createVirtualMachine()
virtualMachineResponder = VnetDelegate()
virtualMachine.delegate = virtualMachineResponder
virtualMachineView.virtualMachine = virtualMachine
virtualMachineView.capturesSystemKeys = true
if #available(macOS 14.0, *) {
// Configure the app to automatically respond to changes in the display size.
virtualMachineView.automaticallyReconfiguresDisplay = true
}
if #available(macOS 14.0, *) {
let fileManager = FileManager.default
if fileManager.fileExists(atPath: saveFileURL.path) {
restoreVirtualMachine()
} else {
startVirtualMachine()
}
} else {
startVirtualMachine()
}
}
#endif
}
// MARK: Save the virtual machine when the app exits.
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
#if arch(arm64)
@available(macOS 14.0, *)
func saveVirtualMachine(completionHandler: @escaping () -> Void) {
virtualMachine.saveMachineStateTo(url: saveFileURL, completionHandler: { (error) in
guard error == nil else {
fatalError("Virtual machine failed to save with \(error!)")
}
completionHandler()
})
}
@available(macOS 14.0, *)
func pauseAndSaveVirtualMachine(completionHandler: @escaping () -> Void) {
virtualMachine.pause(completionHandler: { (result) in
if case let .failure(error) = result {
fatalError("Virtual machine failed to pause with \(error)")
}
self.saveVirtualMachine(completionHandler: completionHandler)
})
}
#endif
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
#if arch(arm64)
if #available(macOS 14.0, *) {
if virtualMachine.state == .running {
pauseAndSaveVirtualMachine(completionHandler: {
sender.reply(toApplicationShouldTerminate: true)
})
return .terminateLater
}
}
#endif
return .terminateNow
}
}

View File

@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,58 @@
{
"images" : [
{
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,696 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22690"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="macOSVirtualMachineSampleApp_Swift" customModuleProvider="target">
<connections>
<outlet property="virtualMachineView" destination="EiT-Mj-1SZ" id="KBI-Ak-yeW"/>
<outlet property="window" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
</connections>
</customObject>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="vnetMacHost" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="vnetMacHost" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About vnetMacHost" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
<menuItem title="Services" id="NMo-om-nkz">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
<menuItem title="Hide vnetMacHost" keyEquivalent="h" id="Olw-nP-bQN">
<connections>
<action selector="hide:" target="-1" id="PnN-Uc-m68"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
</connections>
</menuItem>
<menuItem title="Show All" id="Kd2-mp-pUS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
<menuItem title="Save and quit vnetMacHost" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="File" id="dMs-cI-mzQ">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="File" id="bib-Uj-vzu">
<items>
<menuItem title="New" keyEquivalent="n" id="Was-JA-tGl">
<connections>
<action selector="newDocument:" target="-1" id="4Si-XN-c54"/>
</connections>
</menuItem>
<menuItem title="Open…" keyEquivalent="o" id="IAo-SY-fd9">
<connections>
<action selector="openDocument:" target="-1" id="bVn-NM-KNZ"/>
</connections>
</menuItem>
<menuItem title="Open Recent" id="tXI-mr-wws">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Open Recent" systemMenu="recentDocuments" id="oas-Oc-fiZ">
<items>
<menuItem title="Clear Menu" id="vNY-rz-j42">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="clearRecentDocuments:" target="-1" id="Daa-9d-B3U"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
<menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
<connections>
<action selector="performClose:" target="-1" id="HmO-Ls-i7Q"/>
</connections>
</menuItem>
<menuItem title="Save…" keyEquivalent="s" id="pxx-59-PXV">
<connections>
<action selector="saveDocument:" target="-1" id="teZ-XB-qJY"/>
</connections>
</menuItem>
<menuItem title="Save As…" keyEquivalent="S" id="Bw7-FT-i3A">
<connections>
<action selector="saveDocumentAs:" target="-1" id="mDf-zr-I0C"/>
</connections>
</menuItem>
<menuItem title="Revert to Saved" keyEquivalent="r" id="KaW-ft-85H">
<connections>
<action selector="revertDocumentToSaved:" target="-1" id="iJ3-Pv-kwq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="aJh-i4-bef"/>
<menuItem title="Page Setup…" keyEquivalent="P" id="qIS-W8-SiK">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="runPageLayout:" target="-1" id="Din-rz-gC5"/>
</connections>
</menuItem>
<menuItem title="Print…" keyEquivalent="p" id="aTl-1u-JFS">
<connections>
<action selector="print:" target="-1" id="qaZ-4w-aoO"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Edit" id="5QF-Oa-p0T">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
<items>
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
<connections>
<action selector="undo:" target="-1" id="M6e-cu-g7V"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
<connections>
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
<connections>
<action selector="cut:" target="-1" id="YJe-68-I9s"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
<connections>
<action selector="copy:" target="-1" id="G1f-GL-Joy"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
<connections>
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
</connections>
</menuItem>
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
</connections>
</menuItem>
<menuItem title="Delete" id="pa3-QI-u2k">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
<connections>
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
<menuItem title="Find" id="4EN-yA-p0u">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Find" id="1b7-l0-nxx">
<items>
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
<connections>
<action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
</connections>
</menuItem>
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
</connections>
</menuItem>
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
<connections>
<action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
</connections>
</menuItem>
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
<connections>
<action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
</connections>
</menuItem>
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
<connections>
<action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
</connections>
</menuItem>
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
<connections>
<action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
<items>
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
<connections>
<action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
</connections>
</menuItem>
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
<connections>
<action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
</connections>
</menuItem>
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
</connections>
</menuItem>
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Substitutions" id="9ic-FL-obx">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
<items>
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
</connections>
</menuItem>
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
</connections>
</menuItem>
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
</connections>
</menuItem>
<menuItem title="Smart Links" id="cwL-P1-jid">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
</connections>
</menuItem>
<menuItem title="Data Detectors" id="tRr-pd-1PS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
</connections>
</menuItem>
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Transformations" id="2oI-Rn-ZJC">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
<items>
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
</connections>
</menuItem>
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
</connections>
</menuItem>
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Speech" id="xrE-MZ-jX0">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
<items>
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
</connections>
</menuItem>
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Format" id="jxT-CU-nIS">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Format" id="GEO-Iw-cKr">
<items>
<menuItem title="Font" id="Gi5-1S-RQB">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Font" systemMenu="font" id="aXa-aM-Jaq">
<items>
<menuItem title="Show Fonts" keyEquivalent="t" id="Q5e-8K-NDq">
<connections>
<action selector="orderFrontFontPanel:" target="YLy-65-1bz" id="WHr-nq-2xA"/>
</connections>
</menuItem>
<menuItem title="Bold" tag="2" keyEquivalent="b" id="GB9-OM-e27">
<connections>
<action selector="addFontTrait:" target="YLy-65-1bz" id="hqk-hr-sYV"/>
</connections>
</menuItem>
<menuItem title="Italic" tag="1" keyEquivalent="i" id="Vjx-xi-njq">
<connections>
<action selector="addFontTrait:" target="YLy-65-1bz" id="IHV-OB-c03"/>
</connections>
</menuItem>
<menuItem title="Underline" keyEquivalent="u" id="WRG-CD-K1S">
<connections>
<action selector="underline:" target="-1" id="FYS-2b-JAY"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="5gT-KC-WSO"/>
<menuItem title="Bigger" tag="3" keyEquivalent="+" id="Ptp-SP-VEL">
<connections>
<action selector="modifyFont:" target="YLy-65-1bz" id="Uc7-di-UnL"/>
</connections>
</menuItem>
<menuItem title="Smaller" tag="4" keyEquivalent="-" id="i1d-Er-qST">
<connections>
<action selector="modifyFont:" target="YLy-65-1bz" id="HcX-Lf-eNd"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kx3-Dk-x3B"/>
<menuItem title="Kern" id="jBQ-r6-VK2">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Kern" id="tlD-Oa-oAM">
<items>
<menuItem title="Use Default" id="GUa-eO-cwY">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useStandardKerning:" target="-1" id="6dk-9l-Ckg"/>
</connections>
</menuItem>
<menuItem title="Use None" id="cDB-IK-hbR">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="turnOffKerning:" target="-1" id="U8a-gz-Maa"/>
</connections>
</menuItem>
<menuItem title="Tighten" id="46P-cB-AYj">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="tightenKerning:" target="-1" id="hr7-Nz-8ro"/>
</connections>
</menuItem>
<menuItem title="Loosen" id="ogc-rX-tC1">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="loosenKerning:" target="-1" id="8i4-f9-FKE"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Ligatures" id="o6e-r0-MWq">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Ligatures" id="w0m-vy-SC9">
<items>
<menuItem title="Use Default" id="agt-UL-0e3">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useStandardLigatures:" target="-1" id="7uR-wd-Dx6"/>
</connections>
</menuItem>
<menuItem title="Use None" id="J7y-lM-qPV">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="turnOffLigatures:" target="-1" id="iX2-gA-Ilz"/>
</connections>
</menuItem>
<menuItem title="Use All" id="xQD-1f-W4t">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useAllLigatures:" target="-1" id="KcB-kA-TuK"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Baseline" id="OaQ-X3-Vso">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Baseline" id="ijk-EB-dga">
<items>
<menuItem title="Use Default" id="3Om-Ey-2VK">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unscript:" target="-1" id="0vZ-95-Ywn"/>
</connections>
</menuItem>
<menuItem title="Superscript" id="Rqc-34-cIF">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="superscript:" target="-1" id="3qV-fo-wpU"/>
</connections>
</menuItem>
<menuItem title="Subscript" id="I0S-gh-46l">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="subscript:" target="-1" id="Q6W-4W-IGz"/>
</connections>
</menuItem>
<menuItem title="Raise" id="2h7-ER-AoG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="raiseBaseline:" target="-1" id="4sk-31-7Q9"/>
</connections>
</menuItem>
<menuItem title="Lower" id="1tx-W0-xDw">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowerBaseline:" target="-1" id="OF1-bc-KW4"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="Ndw-q3-faq"/>
<menuItem title="Show Colors" keyEquivalent="C" id="bgn-CT-cEk">
<connections>
<action selector="orderFrontColorPanel:" target="-1" id="mSX-Xz-DV3"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="iMs-zA-UFJ"/>
<menuItem title="Copy Style" keyEquivalent="c" id="5Vv-lz-BsD">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="copyFont:" target="-1" id="GJO-xA-L4q"/>
</connections>
</menuItem>
<menuItem title="Paste Style" keyEquivalent="v" id="vKC-jM-MkH">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteFont:" target="-1" id="JfD-CL-leO"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Text" id="Fal-I4-PZk">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Text" id="d9c-me-L2H">
<items>
<menuItem title="Align Left" keyEquivalent="{" id="ZM1-6Q-yy1">
<connections>
<action selector="alignLeft:" target="-1" id="zUv-R1-uAa"/>
</connections>
</menuItem>
<menuItem title="Center" keyEquivalent="|" id="VIY-Ag-zcb">
<connections>
<action selector="alignCenter:" target="-1" id="spX-mk-kcS"/>
</connections>
</menuItem>
<menuItem title="Justify" id="J5U-5w-g23">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="alignJustified:" target="-1" id="ljL-7U-jND"/>
</connections>
</menuItem>
<menuItem title="Align Right" keyEquivalent="}" id="wb2-vD-lq4">
<connections>
<action selector="alignRight:" target="-1" id="r48-bG-YeY"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="4s2-GY-VfK"/>
<menuItem title="Writing Direction" id="H1b-Si-o9J">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Writing Direction" id="8mr-sm-Yjd">
<items>
<menuItem title="Paragraph" enabled="NO" id="ZvO-Gk-QUH">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem id="YGs-j5-SAR">
<string key="title"> Default</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionNatural:" target="-1" id="qtV-5e-UBP"/>
</connections>
</menuItem>
<menuItem id="Lbh-J2-qVU">
<string key="title"> Left to Right</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionLeftToRight:" target="-1" id="S0X-9S-QSf"/>
</connections>
</menuItem>
<menuItem id="jFq-tB-4Kx">
<string key="title"> Right to Left</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionRightToLeft:" target="-1" id="5fk-qB-AqJ"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="swp-gr-a21"/>
<menuItem title="Selection" enabled="NO" id="cqv-fj-IhA">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem id="Nop-cj-93Q">
<string key="title"> Default</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionNatural:" target="-1" id="lPI-Se-ZHp"/>
</connections>
</menuItem>
<menuItem id="BgM-ve-c93">
<string key="title"> Left to Right</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionLeftToRight:" target="-1" id="caW-Bv-w94"/>
</connections>
</menuItem>
<menuItem id="RB4-Sm-HuC">
<string key="title"> Right to Left</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionRightToLeft:" target="-1" id="EXD-6r-ZUu"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="fKy-g9-1gm"/>
<menuItem title="Show Ruler" id="vLm-3I-IUL">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleRuler:" target="-1" id="FOx-HJ-KwY"/>
</connections>
</menuItem>
<menuItem title="Copy Ruler" keyEquivalent="c" id="MkV-Pr-PK5">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="copyRuler:" target="-1" id="71i-fW-3W2"/>
</connections>
</menuItem>
<menuItem title="Paste Ruler" keyEquivalent="v" id="LVM-kO-fVI">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="pasteRuler:" target="-1" id="cSh-wd-qM2"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="View" id="H8h-7b-M4v">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="View" id="HyV-fh-RgO">
<items>
<menuItem title="Show Toolbar" keyEquivalent="t" id="snW-S8-Cw5">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="toggleToolbarShown:" target="-1" id="BXY-wc-z0C"/>
</connections>
</menuItem>
<menuItem title="Customize Toolbar…" id="1UK-8n-QPP">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="runToolbarCustomizationPalette:" target="-1" id="pQI-g3-MTW"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="hB3-LF-h0Y"/>
<menuItem title="Show Sidebar" keyEquivalent="s" id="kIP-vf-haE">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="toggleSidebar:" target="-1" id="iwa-gc-5KM"/>
</connections>
</menuItem>
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Window" id="aUF-d1-5bR">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
<items>
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
<connections>
<action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
</connections>
</menuItem>
<menuItem title="Zoom" id="R4o-n2-Eq4">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Help" id="wpr-3q-Mcd">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
<items>
<menuItem title="vnetMacHost Help" keyEquivalent="?" id="FKE-Sm-Kum">
<connections>
<action selector="showHelp:" target="-1" id="y7X-2Q-9no"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
<point key="canvasLocation" x="200" y="121"/>
</menu>
<window title="vnetMacHost" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="335" y="390" width="960" height="600"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
<view key="contentView" id="EiT-Mj-1SZ" customClass="VZVirtualMachineView">
<rect key="frame" x="0.0" y="0.0" width="960" height="600"/>
<autoresizingMask key="autoresizingMask"/>
</view>
<point key="canvasLocation" x="200" y="400"/>
</window>
</objects>
</document>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.virtualization</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array/>
</plist>

View File

@@ -0,0 +1,544 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 55;
objects = {
/* Begin PBXBuildFile section */
8F87D52126C34111000EADA4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F87D52026C34111000EADA4 /* AppDelegate.swift */; };
8F87D52326C34111000EADA4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8F87D52226C34111000EADA4 /* Assets.xcassets */; };
8F87D52626C34111000EADA4 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8F87D52426C34111000EADA4 /* MainMenu.xib */; };
8F87D53426C341AC000EADA4 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F87D53326C341AC000EADA4 /* main.swift */; };
8F87D53A26C3423F000EADA4 /* MacOSVirtualMachineInstaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F87D53926C3423F000EADA4 /* MacOSVirtualMachineInstaller.swift */; };
8F87D54026C34259000EADA4 /* VnetHostConfigHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F87D53D26C34259000EADA4 /* VnetHostConfigHelper.swift */; };
8F87D54126C34259000EADA4 /* VnetDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F87D53E26C34259000EADA4 /* VnetDelegate.swift */; };
8F87D54326C34265000EADA4 /* VnetDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F87D53E26C34259000EADA4 /* VnetDelegate.swift */; };
8F87D54426C34269000EADA4 /* VnetHostConfigHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F87D53D26C34259000EADA4 /* VnetHostConfigHelper.swift */; };
8F87D54726C3427C000EADA4 /* Virtualization.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8F87D54626C3427C000EADA4 /* Virtualization.framework */; };
8F87D54826C34286000EADA4 /* Virtualization.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8F87D54626C3427C000EADA4 /* Virtualization.framework */; };
8FB90BEE26D5AC8100988F51 /* MacOSRestoreImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FB90BED26D5AC8100988F51 /* MacOSRestoreImage.swift */; };
C266EA7F2C5D2AD800DC57E3 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = C266EA7E2C5D2AD800DC57E3 /* Config.swift */; };
C266EA802C5D2AE700DC57E3 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = C266EA7E2C5D2AD800DC57E3 /* Config.swift */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
8F87D52F26C341AC000EADA4 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
dstSubfolderSpec = 0;
files = (
);
runOnlyForDeploymentPostprocessing = 1;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
8F87D51D26C34111000EADA4 /* vnetMacHost.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = vnetMacHost.app; sourceTree = BUILT_PRODUCTS_DIR; };
8F87D52026C34111000EADA4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
8F87D52226C34111000EADA4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
8F87D52526C34111000EADA4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
8F87D53126C341AC000EADA4 /* InstallationTool-Swift */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "InstallationTool-Swift"; sourceTree = BUILT_PRODUCTS_DIR; };
8F87D53326C341AC000EADA4 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
8F87D53826C3423F000EADA4 /* InstallationTool.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = InstallationTool.entitlements; sourceTree = "<group>"; };
8F87D53926C3423F000EADA4 /* MacOSVirtualMachineInstaller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacOSVirtualMachineInstaller.swift; sourceTree = "<group>"; };
8F87D53B26C34250000EADA4 /* vnetMacHost.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = vnetMacHost.entitlements; sourceTree = "<group>"; };
8F87D53D26C34259000EADA4 /* VnetHostConfigHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VnetHostConfigHelper.swift; sourceTree = "<group>"; };
8F87D53E26C34259000EADA4 /* VnetDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VnetDelegate.swift; sourceTree = "<group>"; };
8F87D54626C3427C000EADA4 /* Virtualization.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Virtualization.framework; path = System/Library/Frameworks/Virtualization.framework; sourceTree = SDKROOT; };
8FB90BE826D422FD00988F51 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8FB90BED26D5AC8100988F51 /* MacOSRestoreImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacOSRestoreImage.swift; sourceTree = "<group>"; };
B0E246092DFBF28FAEA2709F /* LICENSE.txt */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = LICENSE.txt; sourceTree = "<group>"; };
C266EA7E2C5D2AD800DC57E3 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = "<group>"; };
F4E72614B21833A4E0FE3E98 /* SampleCode.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = SampleCode.xcconfig; path = Configuration/SampleCode.xcconfig; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
8F87D51A26C34111000EADA4 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
8F87D54826C34286000EADA4 /* Virtualization.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
8F87D52E26C341AC000EADA4 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
8F87D54726C3427C000EADA4 /* Virtualization.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
09E329497FB7E44895839D88 /* LICENSE */ = {
isa = PBXGroup;
children = (
B0E246092DFBF28FAEA2709F /* LICENSE.txt */,
);
path = LICENSE;
sourceTree = "<group>";
};
3026A0D16D3A077128FE4194 /* Configuration */ = {
isa = PBXGroup;
children = (
F4E72614B21833A4E0FE3E98 /* SampleCode.xcconfig */,
);
name = Configuration;
sourceTree = "<group>";
};
8F87D51426C34111000EADA4 = {
isa = PBXGroup;
children = (
8F87D53B26C34250000EADA4 /* vnetMacHost.entitlements */,
8F87D53826C3423F000EADA4 /* InstallationTool.entitlements */,
8FDABC17270D0F9100D7FC60 /* Swift */,
8F87D51E26C34111000EADA4 /* Products */,
8F87D54526C3427C000EADA4 /* Frameworks */,
3026A0D16D3A077128FE4194 /* Configuration */,
09E329497FB7E44895839D88 /* LICENSE */,
);
sourceTree = "<group>";
};
8F87D51E26C34111000EADA4 /* Products */ = {
isa = PBXGroup;
children = (
8F87D51D26C34111000EADA4 /* vnetMacHost.app */,
8F87D53126C341AC000EADA4 /* InstallationTool-Swift */,
);
name = Products;
sourceTree = "<group>";
};
8F87D51F26C34111000EADA4 /* vnetMacHost */ = {
isa = PBXGroup;
children = (
8F87D52026C34111000EADA4 /* AppDelegate.swift */,
8F87D52226C34111000EADA4 /* Assets.xcassets */,
8F87D52426C34111000EADA4 /* MainMenu.xib */,
8FB90BE826D422FD00988F51 /* Info.plist */,
);
path = vnetMacHost;
sourceTree = "<group>";
};
8F87D52C26C3418F000EADA4 /* Common */ = {
isa = PBXGroup;
children = (
C266EA7E2C5D2AD800DC57E3 /* Config.swift */,
8F87D53D26C34259000EADA4 /* VnetHostConfigHelper.swift */,
8F87D53E26C34259000EADA4 /* VnetDelegate.swift */,
);
path = Common;
sourceTree = "<group>";
};
8F87D53226C341AC000EADA4 /* InstallationTool */ = {
isa = PBXGroup;
children = (
8FB90BED26D5AC8100988F51 /* MacOSRestoreImage.swift */,
8F87D53926C3423F000EADA4 /* MacOSVirtualMachineInstaller.swift */,
8F87D53326C341AC000EADA4 /* main.swift */,
);
path = InstallationTool;
sourceTree = "<group>";
};
8F87D54526C3427C000EADA4 /* Frameworks */ = {
isa = PBXGroup;
children = (
8F87D54626C3427C000EADA4 /* Virtualization.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
8FDABC17270D0F9100D7FC60 /* Swift */ = {
isa = PBXGroup;
children = (
8F87D52C26C3418F000EADA4 /* Common */,
8F87D51F26C34111000EADA4 /* vnetMacHost */,
8F87D53226C341AC000EADA4 /* InstallationTool */,
);
path = Swift;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
8F87D51C26C34111000EADA4 /* vnetMacHost-Swift */ = {
isa = PBXNativeTarget;
buildConfigurationList = 8F87D52926C34111000EADA4 /* Build configuration list for PBXNativeTarget "vnetMacHost-Swift" */;
buildPhases = (
8F87D51926C34111000EADA4 /* Sources */,
8F87D51A26C34111000EADA4 /* Frameworks */,
8F87D51B26C34111000EADA4 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "vnetMacHost-Swift";
productName = macOSVirtualMachineSampleApp;
productReference = 8F87D51D26C34111000EADA4 /* vnetMacHost.app */;
productType = "com.apple.product-type.application";
};
8F87D53026C341AC000EADA4 /* InstallationTool-Swift */ = {
isa = PBXNativeTarget;
buildConfigurationList = 8F87D53526C341AC000EADA4 /* Build configuration list for PBXNativeTarget "InstallationTool-Swift" */;
buildPhases = (
8F87D52D26C341AC000EADA4 /* Sources */,
8F87D52E26C341AC000EADA4 /* Frameworks */,
8F87D52F26C341AC000EADA4 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = "InstallationTool-Swift";
productName = InstallationTool;
productReference = 8F87D53126C341AC000EADA4 /* InstallationTool-Swift */;
productType = "com.apple.product-type.tool";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
8F87D51526C34111000EADA4 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
DefaultBuildSystemTypeForWorkspace = Latest;
LastSwiftUpdateCheck = 1300;
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = Apple;
TargetAttributes = {
8F87D51C26C34111000EADA4 = {
CreatedOnToolsVersion = 13.0;
};
8F87D53026C341AC000EADA4 = {
CreatedOnToolsVersion = 13.0;
};
};
};
buildConfigurationList = 8F87D51826C34111000EADA4 /* Build configuration list for PBXProject "vnetMacHost" */;
compatibilityVersion = "Xcode 13.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 8F87D51426C34111000EADA4;
productRefGroup = 8F87D51E26C34111000EADA4 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
8F87D51C26C34111000EADA4 /* vnetMacHost-Swift */,
8F87D53026C341AC000EADA4 /* InstallationTool-Swift */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
8F87D51B26C34111000EADA4 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8F87D52326C34111000EADA4 /* Assets.xcassets in Resources */,
8F87D52626C34111000EADA4 /* MainMenu.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
8F87D51926C34111000EADA4 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8F87D52126C34111000EADA4 /* AppDelegate.swift in Sources */,
8F87D54126C34259000EADA4 /* VnetDelegate.swift in Sources */,
C266EA7F2C5D2AD800DC57E3 /* Config.swift in Sources */,
8F87D54026C34259000EADA4 /* VnetHostConfigHelper.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
8F87D52D26C341AC000EADA4 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8F87D54426C34269000EADA4 /* VnetHostConfigHelper.swift in Sources */,
C266EA802C5D2AE700DC57E3 /* Config.swift in Sources */,
8F87D53A26C3423F000EADA4 /* MacOSVirtualMachineInstaller.swift in Sources */,
8FB90BEE26D5AC8100988F51 /* MacOSRestoreImage.swift in Sources */,
8F87D54326C34265000EADA4 /* VnetDelegate.swift in Sources */,
8F87D53426C341AC000EADA4 /* main.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
8F87D52426C34111000EADA4 /* MainMenu.xib */ = {
isa = PBXVariantGroup;
children = (
8F87D52526C34111000EADA4 /* Base */,
);
name = MainMenu.xib;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
8F87D52726C34111000EADA4 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = F4E72614B21833A4E0FE3E98 /* SampleCode.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
8F87D52826C34111000EADA4 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = F4E72614B21833A4E0FE3E98 /* SampleCode.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Release;
};
8F87D52A26C34111000EADA4 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = F4E72614B21833A4E0FE3E98 /* SampleCode.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = vnetMacHost.entitlements;
CODE_SIGN_IDENTITY = "Mac Developer";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = W5364U7YZB;
ENABLE_APP_SANDBOX = NO;
ENABLE_USER_SELECTED_FILES = readwrite;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Swift/vnetMacHost/Info.plist;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSMainNibFile = MainMenu;
INFOPLIST_KEY_NSMicrophoneUsageDescription = "Allow for using audio input devices.";
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.tailscale.vnetMacHost;
PRODUCT_NAME = vnetMacHost;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
8F87D52B26C34111000EADA4 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = F4E72614B21833A4E0FE3E98 /* SampleCode.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = vnetMacHost.entitlements;
CODE_SIGN_IDENTITY = "Mac Developer";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = W5364U7YZB;
ENABLE_APP_SANDBOX = NO;
ENABLE_USER_SELECTED_FILES = readwrite;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Swift/vnetMacHost/Info.plist;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSMainNibFile = MainMenu;
INFOPLIST_KEY_NSMicrophoneUsageDescription = "Allow for using audio input devices.";
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.tailscale.vnetMacHost;
PRODUCT_NAME = vnetMacHost;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
name = Release;
};
8F87D53626C341AC000EADA4 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = F4E72614B21833A4E0FE3E98 /* SampleCode.xcconfig */;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = InstallationTool.entitlements;
CODE_SIGN_IDENTITY = "Mac Developer";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = W5364U7YZB;
ENABLE_USER_SELECTED_FILES = readwrite;
MACOSX_DEPLOYMENT_TARGET = 14.0;
PRODUCT_BUNDLE_IDENTIFIER = com.tailscale.vnetMacHostSetupTool;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
8F87D53726C341AC000EADA4 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = F4E72614B21833A4E0FE3E98 /* SampleCode.xcconfig */;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = InstallationTool.entitlements;
CODE_SIGN_IDENTITY = "Mac Developer";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = W5364U7YZB;
ENABLE_USER_SELECTED_FILES = readwrite;
MACOSX_DEPLOYMENT_TARGET = 14.0;
PRODUCT_BUNDLE_IDENTIFIER = com.tailscale.vnetMacHostSetupTool;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
8F87D51826C34111000EADA4 /* Build configuration list for PBXProject "vnetMacHost" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8F87D52726C34111000EADA4 /* Debug */,
8F87D52826C34111000EADA4 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
8F87D52926C34111000EADA4 /* Build configuration list for PBXNativeTarget "vnetMacHost-Swift" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8F87D52A26C34111000EADA4 /* Debug */,
8F87D52B26C34111000EADA4 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
8F87D53526C341AC000EADA4 /* Build configuration list for PBXNativeTarget "InstallationTool-Swift" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8F87D53626C341AC000EADA4 /* Debug */,
8F87D53726C341AC000EADA4 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 8F87D51526C34111000EADA4 /* Project object */;
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildSystemType</key>
<string>Latest</string>
</dict>
</plist>

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1320"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8F87D53026C341AC000EADA4"
BuildableName = "InstallationTool-Swift"
BlueprintName = "InstallationTool-Swift"
ReferencedContainer = "container:vnetMacHost.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8F87D53026C341AC000EADA4"
BuildableName = "InstallationTool-Swift"
BlueprintName = "InstallationTool-Swift"
ReferencedContainer = "container:vnetMacHost.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8F87D53026C341AC000EADA4"
BuildableName = "InstallationTool-Swift"
BlueprintName = "InstallationTool-Swift"
ReferencedContainer = "container:vnetMacHost.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1320"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8F87D51C26C34111000EADA4"
BuildableName = "vnetMacHost.app"
BlueprintName = "vnetMacHost-Swift"
ReferencedContainer = "container:vnetMacHost.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8F87D51C26C34111000EADA4"
BuildableName = "vnetMacHost.app"
BlueprintName = "vnetMacHost-Swift"
ReferencedContainer = "container:vnetMacHost.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8F87D51C26C34111000EADA4"
BuildableName = "vnetMacHost.app"
BlueprintName = "vnetMacHost-Swift"
ReferencedContainer = "container:vnetMacHost.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SuppressBuildableAutocreation</key>
<dict>
<key>8FDABC39270D1DC600D7FC60</key>
<dict>
<key>primary</key>
<true/>
</dict>
<key>8FDABC58270D1FFE00D7FC60</key>
<dict>
<key>primary</key>
<true/>
</dict>
</dict>
</dict>
</plist>