Skip to content

Commit e71a99e

Browse files
author
neil
committed
add Force Quit button
1 parent 74679a3 commit e71a99e

File tree

2 files changed

+84
-49
lines changed

2 files changed

+84
-49
lines changed

README.md

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,16 @@ anyvm is a single-file tool for bootstrapping BSD and Illumos guests with QEMU o
3131
## 2. Quick start (local)
3232

3333
```bash
34-
python anyvm.py --os freebsd
35-
python anyvm.py --os freebsd --release 14.3
36-
python anyvm.py --os freebsd --release 14.3 --arch aarch64
37-
python anyvm.py --os openbsd --release 7.5 --arch aarch64
38-
python anyvm.py --os solaris
34+
python3 anyvm.py --os freebsd
35+
python3 anyvm.py --os freebsd --release 14.3
36+
python3 anyvm.py --os freebsd --release 14.3 --arch aarch64
37+
python3 anyvm.py --os openbsd --release 7.5 --arch aarch64
38+
python3 anyvm.py --os solaris
3939

40-
python anyvm.py --os freebsd --release 14.3 --arch riscv64
40+
python3 anyvm.py --os freebsd --release 14.3 --arch riscv64
4141

4242
# Run a command inside the VM (everything after `--` is sent to the VM via ssh):
43-
python anyvm.py --os freebsd -- uname -a
43+
python3 anyvm.py --os freebsd -- uname -a
4444
```
4545

4646
## 3. Run in a Docker container
@@ -133,127 +133,127 @@ AnyVM includes a built-in, premium VNC Web UI that allows you to access the VM's
133133

134134
## 9. CLI options (with examples)
135135

136-
All examples below use `python anyvm.py ...`. You can also run `python anyvm.py --help` to see the built-in help.
136+
All examples below use `python3 anyvm.py ...`. You can also run `python3 anyvm.py --help` to see the built-in help.
137137

138138
### Required
139139

140140
- `--os <name>`: Target guest OS (required).
141141
- Supported: `freebsd` / `openbsd` / `netbsd` / `dragonflybsd` / `solaris` / `omnios` / `openindiana` / `haiku`
142142
- Example:
143-
- `python anyvm.py --os freebsd`
143+
- `python3 anyvm.py --os freebsd`
144144

145145
### Release / arch / resources
146146

147147
- `--release <ver>`: Guest release version. If omitted, anyvm auto-selects an available release.
148-
- Example: `python anyvm.py --os freebsd --release 14.3`
148+
- Example: `python3 anyvm.py --os freebsd --release 14.3`
149149

150150
- `--arch <arch>`: Guest architecture.
151151
- Common values: `x86_64` / `aarch64` / `riscv64`
152-
- Example: `python anyvm.py --os openbsd --release 7.5 --arch aarch64`
152+
- Example: `python3 anyvm.py --os openbsd --release 7.5 --arch aarch64`
153153

154154
- `--mem <MB>`: Memory size in MB (default: 2048).
155-
- Example: `python anyvm.py --os freebsd --mem 4096`
155+
- Example: `python3 anyvm.py --os freebsd --mem 4096`
156156

157157
- `--cpu <num>`: vCPU count (default: all host cores).
158-
- Example: `python anyvm.py --os freebsd --cpu 4`
158+
- Example: `python3 anyvm.py --os freebsd --cpu 4`
159159

160160
- `--cpu-type <type>`: QEMU CPU model (e.g. `host`, `cortex-a72`).
161-
- Example: `python anyvm.py --os openbsd --arch aarch64 --cpu-type cortex-a72`
161+
- Example: `python3 anyvm.py --os openbsd --arch aarch64 --cpu-type cortex-a72`
162162

163163
### Images / builders
164164

165165
- `--builder <ver>`: Pin a specific builder version (used to download matching cloud images).
166-
- Example: `python anyvm.py --os netbsd --builder 2.0.1`
166+
- Example: `python3 anyvm.py --os netbsd --builder 2.0.1`
167167

168168
- `--qcow2 <path>`: Use a local qcow2 image (skip downloading).
169-
- Example: `python anyvm.py --os freebsd --qcow2 .\\output\\freebsd\\freebsd-14.3.qcow2`
169+
- Example: `python3 anyvm.py --os freebsd --qcow2 .\\output\\freebsd\\freebsd-14.3.qcow2`
170170

171171
- `--snapshot`: Enable QEMU snapshot mode. Changes made to the disk are not saved.
172172
- Works with `--cache-dir` to run directly from the cache without copying to the data directory.
173-
- Example: `python anyvm.py --os freebsd --snapshot`
173+
- Example: `python3 anyvm.py --os freebsd --snapshot`
174174

175175
### Networking (user-mode networking / slirp)
176176

177177
- `--ssh-port <port>` / `--sshport <port>`: Host port forwarded to guest SSH (`:22`). If omitted, anyvm auto-picks a free port.
178-
- Example: `python anyvm.py --os freebsd --ssh-port 10022`
178+
- Example: `python3 anyvm.py --os freebsd --ssh-port 10022`
179179

180180
- `--ssh-name <name>`: Add an extra SSH alias name for convenience (so you can `ssh <name>`).
181-
- Example: `python anyvm.py --os freebsd --ssh-name myvm`
181+
- Example: `python3 anyvm.py --os freebsd --ssh-name myvm`
182182

183183
- `--host-ssh-port <port>`: The host SSH port as reachable from the guest (default: 22). Used for generating a `Host host` entry inside the guest.
184-
- Example: `python anyvm.py --os freebsd --host-ssh-port 2222`
184+
- Example: `python3 anyvm.py --os freebsd --host-ssh-port 2222`
185185

186186
- `-p <mapping>`: Additional port forwards (repeatable).
187187
- Form 1: `host:guest` (TCP by default)
188-
- Example: `python anyvm.py --os freebsd -p 8080:80`
188+
- Example: `python3 anyvm.py --os freebsd -p 8080:80`
189189
- Form 2: `tcp:host:guest`
190-
- Example: `python anyvm.py --os freebsd -p tcp:8443:443`
190+
- Example: `python3 anyvm.py --os freebsd -p tcp:8443:443`
191191
- Form 3: `udp:host:guest`
192-
- Example: `python anyvm.py --os freebsd -p udp:5353:5353`
192+
- Example: `python3 anyvm.py --os freebsd -p udp:5353:5353`
193193

194194
- `--public`: Listen on `0.0.0.0` for forwarded ports instead of `127.0.0.1`.
195-
- Example: `python anyvm.py --os freebsd --public -p 8080:80`
195+
- Example: `python3 anyvm.py --os freebsd --public -p 8080:80`
196196

197197
- `--enable-ipv6`: Enable IPv6 in QEMU user networking (slirp).
198198
- Default: IPv6 is disabled (anyvm adds `ipv6=off` to `-netdev user,...`).
199-
- Example: `python anyvm.py --os freebsd --enable-ipv6`
199+
- Example: `python3 anyvm.py --os freebsd --enable-ipv6`
200200

201201

202202
### Shared folders (-v) and sync mode (--sync)
203203

204204
- `-v <host:guest>`: Add a shared/synced folder mapping (repeatable).
205-
- Linux/macOS example: `python anyvm.py --os freebsd -v $(pwd):/data`
206-
- Windows example: `python anyvm.py --os freebsd -v D:\\data:/data`
205+
- Linux/macOS example: `python3 anyvm.py --os freebsd -v $(pwd):/data`
206+
- Windows example: `python3 anyvm.py --os freebsd -v D:\\data:/data`
207207

208208
- `--sync <mode>`: Sync mechanism used for `-v`.
209209
- Supported: `sshfs` (default), `nfs`, `rsync`, `scp`
210210
- Examples:
211-
- `python anyvm.py --os freebsd --sync rsync -v $(pwd):/data`
212-
- `python anyvm.py --os solaris --sync scp -v D:\\data:/data`
211+
- `python3 anyvm.py --os freebsd --sync rsync -v $(pwd):/data`
212+
- `python3 anyvm.py --os solaris --sync scp -v D:\\data:/data`
213213

214214
### Console / display / debugging
215215

216216
- `--console` / `-c`: Run in the foreground (console mode).
217-
- Example: `python anyvm.py --os freebsd --console`
217+
- Example: `python3 anyvm.py --os freebsd --console`
218218

219219
- `--detach` / `-d`: Run in the background (do not auto-enter SSH).
220-
- Example: `python anyvm.py --os freebsd --detach`
220+
- Example: `python3 anyvm.py --os freebsd --detach`
221221

222222
- `--serial <port>`: Expose the guest serial console via a host TCP port (if omitted, auto-select starting at 7000).
223-
- Example: `python anyvm.py --os freebsd --serial 7000`
223+
- Example: `python3 anyvm.py --os freebsd --serial 7000`
224224

225225
- `--vnc <display>`: Enable VNC (e.g. `0` means `:0` / port 5900).
226226
- **VNC Web UI**: Enabled by default starting at port `6080` (auto-increments if busy). Use `--vnc off` to disable.
227-
- Example: `python anyvm.py --os freebsd --vnc 0`
227+
- Example: `python3 anyvm.py --os freebsd --vnc 0`
228228

229229
- `--mon <port>`: Expose the QEMU monitor via telnet on localhost.
230-
- Example: `python anyvm.py --os freebsd --mon 4444`
230+
- Example: `python3 anyvm.py --os freebsd --mon 4444`
231231

232232
- `--debug`: Enable verbose debug logging.
233-
- Example: `python anyvm.py --os freebsd --debug`
233+
- Example: `python3 anyvm.py --os freebsd --debug`
234234

235235
### Boot / platform
236236

237237
- `--uefi`: Enable UEFI boot (FreeBSD enables this implicitly).
238-
- Example: `python anyvm.py --os freebsd --uefi`
238+
- Example: `python3 anyvm.py --os freebsd --uefi`
239239

240240
- `--disktype <type>`: Disk interface type (e.g. `virtio`, `ide`).
241-
- Example: `python anyvm.py --os dragonflybsd --disktype ide`
241+
- Example: `python3 anyvm.py --os dragonflybsd --disktype ide`
242242

243243
- `--whpx`: (Windows) Attempt to use WHPX acceleration.
244-
- Example: `python anyvm.py --os freebsd --whpx`
244+
- Example: `python3 anyvm.py --os freebsd --whpx`
245245

246246
### Data directory
247247

248248
- `--data-dir <dir>` / `--workingdir <dir>`: Directory used to store images and caches (default: `./output`).
249-
- Example: `python anyvm.py --os freebsd --data-dir .\\output`
249+
- Example: `python3 anyvm.py --os freebsd --data-dir .\\output`
250250

251251
### Run a command inside the VM
252252

253253
- `-- <cmd...>`: Everything after `--` is passed through to the final `ssh` invocation and executed inside the VM.
254254
- Examples:
255-
- `python anyvm.py --os freebsd -- uname -a`
256-
- `python anyvm.py --os freebsd -- sh -lc "id; uname -a"`
255+
- `python3 anyvm.py --os freebsd -- uname -a`
256+
- `python3 anyvm.py --os freebsd -- sh -lc "id; uname -a"`
257257

258258

259259

anyvm.py

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,11 @@ def fatal(msg):
378378
color: #f1f5f9;
379379
transform: translateY(-1px);
380380
}
381+
button.danger:hover {
382+
border-color: #ef4444 !important;
383+
background: rgba(239, 68, 68, 0.1) !important;
384+
color: #ef4444 !important;
385+
}
381386
.toolbar:hover button {
382387
background: rgba(51, 65, 85, 0.8);
383388
color: #f1f5f9;
@@ -495,10 +500,14 @@ def fatal(msg):
495500
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 4v6h-6"></path><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path></svg>
496501
Reboot
497502
</button>
498-
<button onclick="shutdownVM()" title="Shutdown">
503+
<button onclick="shutdownVM()" title="Shutdown (ACPI)">
499504
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18.36 6.64a9 9 0 1 1-12.73 0"></path><line x1="12" y1="2" x2="12" y2="12"></line></svg>
500505
Shutdown
501506
</button>
507+
<button class="danger" onclick="forceShutdownVM()" title="Force Kill VM (Direct Quit)">
508+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"></path></svg>
509+
Force Quit
510+
</button>
502511
</div>
503512
<button onclick="toggleFullscreen()">
504513
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"></path></svg>
@@ -1492,12 +1501,20 @@ def fatal(msg):
14921501
14931502
function shutdownVM() {
14941503
if (!connected || !ws) return;
1495-
if (confirm('Are you sure you want to shutdown the VM?')) {
1504+
if (confirm('Are you sure you want to send a ACPI shutdown signal to the VM?')) {
14961505
// [255, 2, 2] for system_powerdown
14971506
ws.send(new Uint8Array([255, 2, 2]));
14981507
}
14991508
}
15001509
1510+
function forceShutdownVM() {
1511+
if (!connected || !ws) return;
1512+
if (confirm('DANGER: This will immediately KILL the VM process. Unsaved data will be lost. Continue?')) {
1513+
// [255, 2, 3] for quit
1514+
ws.send(new Uint8Array([255, 2, 3]));
1515+
}
1516+
}
1517+
15011518
setInterval(() => {
15021519
const now = performance.now();
15031520
const dt = now - lastFpsTime;
@@ -1687,11 +1704,20 @@ async def ws_to_serial_bridge():
16871704
frame = await self.read_ws_frame(reader)
16881705
if frame is None: break
16891706

1690-
if (len(frame) >= 3 and frame[0] == 255 and frame[1] == 2):
1707+
if frame[0] == 255 and frame[1] == 2 and len(frame) >= 3:
16911708
operation = frame[2]
16921709
if self.qmon_port:
1693-
cmd = "system_reset" if operation == 1 else "system_powerdown"
1694-
asyncio.create_task(self.send_monitor_command(cmd))
1710+
if operation == 1:
1711+
cmd = "system_reset"
1712+
elif operation == 2:
1713+
cmd = "system_powerdown"
1714+
elif operation == 3:
1715+
cmd = "quit"
1716+
else:
1717+
cmd = None
1718+
1719+
if cmd:
1720+
asyncio.create_task(self.send_monitor_command(cmd))
16951721
continue
16961722

16971723
if self.serial_writer:
@@ -1729,11 +1755,20 @@ async def ws_to_vnc():
17291755

17301756
# Intercept custom control messages [255, 2, operation]
17311757
# 1: system_reset, 2: system_powerdown
1732-
if (len(frame) >= 3 and frame[0] == 255 and frame[1] == 2):
1758+
if frame[0] == 255 and frame[1] == 2 and len(frame) >= 3:
17331759
operation = frame[2]
17341760
if self.qmon_port:
1735-
cmd = "system_reset" if operation == 1 else "system_powerdown"
1736-
asyncio.create_task(self.send_monitor_command(cmd))
1761+
if operation == 1:
1762+
cmd = "system_reset"
1763+
elif operation == 2:
1764+
cmd = "system_powerdown"
1765+
elif operation == 3:
1766+
cmd = "quit"
1767+
else:
1768+
cmd = None
1769+
1770+
if cmd:
1771+
asyncio.create_task(self.send_monitor_command(cmd))
17371772
continue
17381773

17391774
vnc_writer.write(frame)

0 commit comments

Comments
 (0)