Skip to content

Commit 0e75ae7

Browse files
retlehssuperdav42bdkabiruddinswalkinshawclaude
authored
Add Linux support for Lima VMs (#659)
Co-authored-by: David Stone <david@nnucomputerwhiz.com> Co-authored-by: Md Kabir Uddin <bd.kabiruddin@gmail.com> Co-authored-by: Scott Walkinshaw <scott.walkinshaw@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ca113d4 commit 0e75ae7

10 files changed

Lines changed: 518 additions & 118 deletions

File tree

cmd/vm_start.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ Usage: trellis vm start [options]
109109
Starts a development virtual machine.
110110
If a VM doesn't exist yet, it will be created. If a VM already exists, it will be started.
111111
112-
Note: VM management (under the 'trellis vm' subcommands) is currently only available for macOS Ventura (13.0) and later.
113-
Lima (https://lima-vm.io/) is the underlying VM manager which requires macOS's new virtualization framework.
112+
Lima (https://lima-vm.io/) is the underlying VM manager.
113+
Local VM support requires macOS 13.0+ or Linux with Lima and QEMU/KVM.
114114
115115
Options:
116116
-h, --help show this help

help.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func deprecatedCommandHelpFunc(commandNames []string, f cli.HelpFunc) cli.HelpFu
4242
commandFunc := commands[key]
4343
command, _ := commandFunc()
4444
key = fmt.Sprintf("%s%s", key, strings.Repeat(" ", maxKeyLen-len(key)))
45-
buf.WriteString(fmt.Sprintf(" %s %s\n", key, command.Synopsis()))
45+
fmt.Fprintf(&buf, " %s %s\n", key, command.Synopsis())
4646
}
4747

4848
return f(filteredCommands) + buf.String()

pkg/lima/files/config.yml

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
vmType: "vz"
1+
vmType: "{{ .VMType }}"
2+
{{- if eq .VMType "vz" }}
23
rosetta:
34
enabled: false
5+
{{- end }}
46
images:
57
{{ range $image := .Config.Images -}}
68
- location: {{ $image.Location }}
@@ -11,13 +13,26 @@ mounts:
1113
- location: {{ $site.AbsLocalPath }}
1214
mountPoint: /srv/www/{{ $siteName }}/current
1315
writable: true
16+
{{- if eq $.VMType "qemu" }}
17+
9p:
18+
securityModel: "mapped-xattr"
19+
{{- end }}
1420
{{ end }}
21+
{{- if eq .VMType "vz" }}
1522
mountType: "virtiofs"
23+
{{- else }}
24+
mountType: "9p"
25+
{{- end }}
1626
ssh:
1727
forwardAgent: true
1828
loadDotSSHPubKeys: true
29+
{{- if eq .VMType "vz" }}
1930
networks:
2031
- vzNAT: true
32+
{{- else }}
33+
networks:
34+
- lima: user-v2
35+
{{- end }}
2136
{{ if .Config.PortForwards }}
2237
portForwards:
2338
{{ range $port := .Config.PortForwards -}}
@@ -32,3 +47,11 @@ provision:
3247
script: |
3348
#!/bin/bash
3449
echo "127.0.0.1 $(hostname)" >> /etc/hosts
50+
{{- if eq .VMType "qemu" }}
51+
52+
TAP_IFACE="$(ip -o link show | awk '/52:54:00:12:34:56/ {print $2}' | tr -d ':' | head -n1)"
53+
if [ -n "$TAP_IFACE" ]; then
54+
ip link set "$TAP_IFACE" up
55+
ip addr add 192.168.56.5/24 dev "$TAP_IFACE" 2>/dev/null || true
56+
fi
57+
{{- end }}

pkg/lima/instance.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ type Instance struct {
5252
Memory int `json:"memory"`
5353
Disk int `json:"disk"`
5454
SshLocalPort int `json:"sshLocalPort,omitempty"`
55+
VMType string `json:"vmType,omitempty"`
5556
Config Config `json:"config"`
5657
Username string `json:"username,omitempty"`
5758
}
@@ -113,24 +114,32 @@ Gets the IP address of the instance using the output of `ip route`:
113114
192.168.64.1 proto dhcp scope link src 192.168.64.2 metric 100
114115
*/
115116
func (i *Instance) IP() (ip string, err error) {
116-
output, err := command.Cmd(
117-
"limactl",
118-
[]string{"shell", "--workdir", "/", i.Name, "ip", "route", "show", "dev", "lima0"},
119-
).CombinedOutput()
117+
args := []string{"shell", "--workdir", "/", i.Name, "ip", "route", "show", "dev", "lima0"}
118+
if i.VMType == "qemu" {
119+
args = []string{"shell", "--workdir", "/", i.Name, "ip", "route", "show"}
120+
}
121+
122+
output, err := command.Cmd("limactl", args).CombinedOutput()
120123

121124
if err != nil {
122125
return "", fmt.Errorf("%w: %v\n%s", IpErr, err, string(output))
123126
}
124127

128+
if i.VMType == "qemu" {
129+
reCustom := regexp.MustCompile(`src (192\.168\.56\.[0-9]+)`)
130+
customMatches := reCustom.FindStringSubmatch(string(output))
131+
if len(customMatches) >= 2 {
132+
return customMatches[1], nil
133+
}
134+
}
135+
125136
re := regexp.MustCompile(`default via .* src ([0-9\.]+)`)
126137
matches := re.FindStringSubmatch(string(output))
127138
if len(matches) < 2 {
128139
return "", fmt.Errorf("%w: no IP address could be matched in the ip route output\n%s", IpErr, string(output))
129140
}
130141

131-
ip = matches[1]
132-
133-
return ip, nil
142+
return matches[1], nil
134143
}
135144

136145
func (i *Instance) Running() bool {

0 commit comments

Comments
 (0)