@tikoci/quickchr 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.bun-version ADDED
@@ -0,0 +1 @@
1
+ 1.4.4
package/DESIGN.md CHANGED
@@ -245,7 +245,9 @@ CI, integration and release all install, enforced by `scripts/check-bun-pin.ts`
245
245
  runtime could change between two identical commits — and did: a six-week gap with no `ci.yml`
246
246
  runs spanned the Bun 1.4.0 release, and the next PR inherited a 1.3.14 → 1.4.2 jump as if it
247
247
  were its own regression. Anything that can turn the build red is a dependency and belongs in
248
- a reviewable diff; "whatever is newest at run time" is not a version.
248
+ a reviewable diff; "whatever is newest at run time" is not a version. The same
249
+ `.bun-version` file ships in the npm package so `doctor` can name the tested
250
+ runtime and warn when the running Bun is older without duplicating the pin.
249
251
 
250
252
  **The 2026-07 refactor (#29)** replaced the organically-grown scheme (integration on every
251
253
  PR push, three parallel copies of the runner logic, `continue-on-error` green-washing)
@@ -1174,3 +1176,39 @@ before acting, and uses shared stopped-state cleanup without recursively taking
1174
1176
  the non-reentrant stop lock. It confirms QEMU exit before clearing the PID or
1175
1177
  releasing named-network membership. Lock wait, hard-stop escalation and final
1176
1178
  exit verification are separate bounded phases outside the guest grace budget.
1179
+
1180
+ ## Frozen boot + continue (#214)
1181
+
1182
+ Some lab workflows must touch the guest before RouterOS init runs. The driver:
1183
+ CHR's `:beep` speaker driver is skipped when CPUID reports a hypervisor, and
1184
+ patching the 2-byte gate in guest RAM via gdbstub before sys2 init unlocks
1185
+ PC-speaker capture for acoustic testing (verified on CHR 7.25, QEMU 11.1.2).
1186
+ `start --frozen` boots held at reset (`-S`) with a gdbstub; `quickchr continue`
1187
+ resumes. The RAM patch itself stays in the harness, not quickchr.
1188
+
1189
+ The gdbstub is a unix socket (`-gdb unix:<machineDir>/gdb.sock,server=on,
1190
+ wait=off`), not `-s` (TCP :1234): no port bookkeeping, no parallel-VM
1191
+ collisions. `wait=off` is required so the spawn never blocks waiting for a
1192
+ client. Measured: `info status` reads `paused (prelaunch)`; gdb attaches with
1193
+ the CPU at the reset vector `0xfff0`. POSIX-first by scope — a Windows TCP
1194
+ fallback (e.g. `portBase+9`) is parked as a follow-up, and the integration test
1195
+ is gated off win32 until it lands.
1196
+
1197
+ `frozen` is per-boot intent, like `--no-wait`, not persisted config: stopping
1198
+ clears the marker and a later plain `start` boots normally. `status` stays
1199
+ `"running"` while frozen — without a third status value — so `isMachineRunning()`,
1200
+ `stop`/`remove` (host-side SIGTERM works on frozen QEMU unchanged), and the
1201
+ `--all` filters keep working untouched. A never-continued frozen machine can
1202
+ never produce `BOOT_TIMEOUT`, by construction: the boot wait is skipped
1203
+ entirely, and `start` returns after the spawn early-exit check.
1204
+
1205
+ `continue` is one existing primitive — `monitorCommand("cont")` — plus the
1206
+ promise `start` makes: REST-ready by default, `--no-wait` to return
1207
+ immediately, `--timeout-extra` extending the resume wait like `start`. It
1208
+ checks `info status` first, so a guest gdb already resumed just clears the
1209
+ marker (idempotent); a QEMU-level `cont` error throws `PROCESS_FAILED` before
1210
+ the marker is touched (same `/^error/i` contract as savevm/loadvm); a dead pid
1211
+ is `MACHINE_STOPPED`; a wait that never reaches REST is a genuine
1212
+ `BOOT_TIMEOUT` with `preserve-existing` cleanup. Deliberately out: no frozen-specific hint on `exec`/`get` failures,
1213
+ and no `savevm` refusal on prelaunch VMs (snapshot is already tricky business;
1214
+ QEMU accepts or errors on its own).
package/MANUAL.md CHANGED
@@ -64,7 +64,7 @@ by the user (or their AI agent) using `quickchr` as a building block.
64
64
  | Bun ≥ 1.1 | runtime | macOS/Linux: `curl -fsSL https://bun.sh/install \| bash` · Windows: `powershell -c "irm bun.sh/install.ps1 \| iex"` (or `winget install Oven-sh.Bun`) |
65
65
  | `qemu-system-x86_64` | x86 CHR | brew/apt/dnf/pacman · Windows: `winget install SoftwareFreedomConservancy.QEMU` |
66
66
  | `qemu-system-aarch64` | arm64 CHR | brew/apt/dnf/pacman · Windows: bundled in the SFC QEMU package above |
67
- | `qemu-img` | `--boot-size`, `--add-disk`, snapshots | included in all standard QEMU packages (brew, apt, dnf, SFC installer) |
67
+ | `qemu-img` | Default qcow2 creation, setup wizard, `--boot-size`, `--add-disk`, snapshots | included in all standard QEMU packages (brew, apt, dnf, SFC installer) |
68
68
  | UEFI firmware (edk2) | arm64 CHR | `qemu-efi-aarch64` (apt), `edk2-aarch64` (dnf), bundled with `qemu` (brew) |
69
69
  | `socket_vmnet` (optional) | rootless `shared`/`bridged` networking on macOS | `brew install socket_vmnet` |
70
70
  | `socat` (optional) | piping serial console externally | brew/apt |
@@ -116,7 +116,9 @@ The `--json` form emits `{ ok, checks, staleImages }`, where `checks` is an
116
116
  array of `{ label, status, detail }` and `staleImages` lists cached images older
117
117
  than the current long-term release. The exit code still reflects `ok`.
118
118
 
119
- ![quickchr doctor output](./images/doctor-demo.gif)
119
+ `doctor` reports the tested Bun pin and warns when the running runtime is older.
120
+
121
+ ![quickchr doctor output](./docs/images/doctor-demo.gif)
120
122
 
121
123
  Use this whenever a `start` fails — the diagnostic almost always points
122
124
  at a missing dependency, missing firmware, or insufficient disk.
@@ -125,7 +127,7 @@ at a missing dependency, missing firmware, or insufficient disk.
125
127
 
126
128
  ## 3. CLI command reference
127
129
 
128
- The dispatcher lives in `src/cli/index.ts` (lines 121–194). Help text is
130
+ The dispatcher lives in `src/cli/index.ts`. Help text is
129
131
  available for every command via `quickchr help <command>`.
130
132
 
131
133
  ### Lifecycle
@@ -138,14 +140,14 @@ directory, downloads the RouterOS image (cached), and writes
138
140
 
139
141
  | Flag | Default | Notes |
140
142
  |---|---|---|
141
- | `--name <s>` | auto | `host-arch-N` if omitted |
143
+ | `--name <s>` | auto | `version-arch-N` if omitted |
142
144
  | `--version <ver>` | latest in channel | e.g., `7.22.1` |
143
145
  | `--channel <ch>` | `stable` | `stable`, `long-term`, `testing`, `development` |
144
146
  | `--arch <a>` | host native | `arm64`, `x86`, or `auto` |
145
147
  | `--cpu <n>` | `1` | vCPU count |
146
148
  | `--mem <mb>` | `512` | RAM (cross-arch TCG bumps to `1024`) |
147
149
  | `--boot-disk-format <f>` | `qcow2` | `qcow2` or `raw` |
148
- | `--boot-size <size>` | — | Resize boot disk; needs `qemu-img`; auto-converts to qcow2 |
150
+ | `--boot-size <size>` | — | Resize qcow2 boot disk; needs `qemu-img`; explicit raw is refused |
149
151
  | `--add-disk <size>` | — | Extra blank qcow2 disk; repeatable |
150
152
  | `--forward <spec>` | — | Extra hostfwd port or built-in service host-port pin; repeatable. See §Custom port forwards. |
151
153
  | `--add-package <pkg>` | — | Provisioning, repeatable, ≥ 7.20.8 |
@@ -165,6 +167,7 @@ directory, downloads the RouterOS image (cached), and writes
165
167
  | `--no-winbox` | false | Exclude WinBox port |
166
168
  | `--no-api-ssl` | false | Exclude API-SSL port |
167
169
  | `--port-base <n>` | auto from 9100 | First port of the 10-port block |
170
+ | `--qemu-arg <arg>` | — | Extra QEMU argv element, repeatable (space form only). Recorded and reused every boot. See Extra QEMU arguments below. |
168
171
 
169
172
  `--vmnet-shared` and `--vmnet-bridge <iface>` have been removed. Use
170
173
  `--add-network shared` and `--add-network bridged:<iface>`; either old spelling is
@@ -192,7 +195,9 @@ Additional flags:
192
195
  | `--fg` / `--foreground` (alias `--no-bg`, `--no-background`) | Serial console on stdio |
193
196
  | `--bg` / `--background` | **Deprecated, does nothing.** Background is the default, and the flag never made `start` return early — it only ever chose where the serial console went. Prints a warning; use `--no-wait` |
194
197
  | `--timeout-extra <s>` / `-T <s>` | Add extra seconds to computed boot timeout |
195
- | `--dry-run` | Print resolved options and QEMU command, exit |
198
+ | `--dry-run` | Print the resolved start options as JSON (including `qemuArgs`/`frozen`) and exit — not the final QEMU argv |
199
+ | `--qemu-arg <arg>` | Extra QEMU argv element, repeatable (space form only). Recorded when creating; on an existing machine it replaces recorded args for this boot only. Refused with `--all`. |
200
+ | `--frozen` | Boot frozen at reset (`-S`) with a gdbstub; resume with `continue`. Refused with `--fg`, provisioning, and `--all` |
196
201
 
197
202
  After `--no-wait`, readiness is yours. centrs (0.1.8+) waits for it in one command:
198
203
 
@@ -216,6 +221,62 @@ When `--fg` is combined with provisioning options, quickchr boots
216
221
  foreground with stdio attached. This is observable as a brief stop in
217
222
  machine state.
218
223
 
224
+ **Extra QEMU arguments (`--qemu-arg`).** One argv element per occurrence,
225
+ appended verbatim (no splitting, no validation) after quickchr's own args.
226
+ Use the space form: `--qemu-arg=x` overwrites instead of accumulating, so
227
+ mixing space and `=` forms keeps only the `=` values. Values beginning with
228
+ `--` need the `--qemu-arg=<arg>` form (one element per flag).
229
+
230
+ Passthrough runs with your privileges and is replayed every boot: it can
231
+ touch any path you can (`-drive`, `-audiodev`) or open QEMU-side listeners,
232
+ so treat recorded args like config you own. A bad recorded arg fails every
233
+ start; recover with a session override (`start <name> --qemu-arg …`) and then
234
+ persist the fix with `set <name> --qemu-arg …` (or `--clear-qemu-args`) while
235
+ stopped.
236
+ Ordering is load-bearing — passthrough goes last, so a conflicting option
237
+ resolves last-wins (a duplicate `-M pc,...` merges over quickchr's `-M pc`;
238
+ measured on QEMU 11.1.2). A bad element surfaces as `SPAWN_FAILED` with the
239
+ QEMU log tail. Lab escape hatch for QEMU surface quickchr does not model —
240
+ e.g. capturing the CHR PC speaker (x86 only) to a WAV file:
241
+
242
+ ```sh
243
+ quickchr start beep-lab --version 7.25 --arch x86 \
244
+ --qemu-arg -M --qemu-arg pc,pcspk-audiodev=cap \
245
+ --qemu-arg -audiodev --qemu-arg wav,id=cap,path=$PWD/cap.wav
246
+ # cap.wav is created at spawn (0 bytes while running); QEMU flushes the
247
+ # 44-byte RIFF header on exit. Capturing actual beeps also needs the
248
+ # --frozen RAM-patch workflow below: stock CHR skips :beep under a
249
+ # hypervisor, so an unpatched guest never emits audio.
250
+ ```
251
+
252
+ #### `continue <name> [--no-wait] [--timeout-extra <s>]`
253
+
254
+ Resume a machine started with `start --frozen` (held at reset with a
255
+ gdbstub). Sends one monitor `cont`, then waits for RouterOS to become
256
+ ready — the same promise `start` makes. `--no-wait` returns right after
257
+ resuming; `--timeout-extra`/`-T` extends the resume timeout like `start`. Idempotent: when the guest is already running (e.g. gdb
258
+ continued it), this just clears the frozen marker. A machine that never
259
+ reaches readiness is a `BOOT_TIMEOUT`; it is stopped and preserved.
260
+
261
+ `--frozen` exists for harnesses that must touch the guest before RouterOS
262
+ init runs — e.g. patching guest RAM via gdb, then resuming:
263
+
264
+ ```sh
265
+ quickchr start beep-lab --version 7.25 --arch x86 --frozen
266
+ # frozen at reset; gdb at <machineDir>/gdb.sock (see 'quickchr list beep-lab')
267
+ gdb -ex "target remote /path/to/machines/beep-lab/gdb.sock" \
268
+ -ex "set *(char *)0xADDR = 0x.." -ex detach -ex quit
269
+ quickchr continue beep-lab
270
+ # resumed, REST-ready
271
+ ```
272
+
273
+ Frozen is per-boot intent, not config: `stop` clears it and a later plain
274
+ `start` boots normally. `list` marks frozen machines (`(frozen)` in the
275
+ table, a `Frozen:` line plus the gdb socket path in the detail view).
276
+ Note the vocabulary overlap: this `frozen` (QEMU held at reset) is a
277
+ different domain from QGA `fsfreeze-status` (guest filesystems frozen for
278
+ snapshots).
279
+
219
280
  #### `stop [name | --all]`
220
281
 
221
282
  Power cut: sends SIGTERM to QEMU, waits up to 5 seconds, then SIGKILL if
@@ -320,7 +381,10 @@ repeat the original command rather than to run a bare `start`.
320
381
 
321
382
  Without a name: tabular summary of every machine. With a name: detailed
322
383
  view including credentials and connection tips. `--json` for
323
- machine-readable output.
384
+ machine-readable output. A machine held by `start --frozen` shows
385
+ `(frozen)` in the table and a `Frozen:` line with its gdb socket path in
386
+ the detail view; single-machine `--json` carries `frozen` (and the recorded
387
+ `qemuArgs`) alongside the other fields.
324
388
 
325
389
  A machine whose `machine.json` cannot be read — unreadable on disk, not valid JSON, or
326
390
  valid JSON that is not usable machine state — still appears, as a row marked
@@ -447,6 +511,8 @@ flags that were asked for so it can be run as given.
447
511
  | `--device-mode <m>` | `rose`, `advanced`, `basic`, `home`, `auto`. |
448
512
  | `--device-mode-enable <f,…>` | Set one or more device-mode flags to yes. Repeatable, comma-separated. |
449
513
  | `--device-mode-disable <f,…>` | Set one or more to no. |
514
+ | `--qemu-arg <arg>` | Replace the recorded QEMU passthrough args (repeatable, space-form only). Stopped only; takes effect on the next start. |
515
+ | `--clear-qemu-args` | Drop the recorded QEMU passthrough args. Stopped only. |
450
516
 
451
517
  **Device-mode restarts the machine.** RouterOS only applies a device-mode change
452
518
  across a power cycle, and quickchr owns the power button — which is why this lives
@@ -486,7 +552,7 @@ Interactive wizard (`@clack/prompts`) that walks through all machine creation
486
552
  options and starts the CHR. Triggered by `quickchr setup` or automatically
487
553
  when `quickchr` is run with no subcommand on a TTY.
488
554
 
489
- ![quickchr setup wizard walkthrough](./images/wizard-demo.gif)
555
+ ![quickchr setup wizard walkthrough](./docs/images/wizard-demo.gif)
490
556
 
491
557
  **First-run detection**: if no machines exist yet, the wizard opens with a
492
558
  welcome message before the first prompt.
@@ -513,7 +579,10 @@ another machine, view status, or quit.
513
579
  | 12 | Run mode: background / foreground | background | (default) / `--fg` |
514
580
  | 13 | Final confirmation | — | — |
515
581
 
516
- Steps 7–9 are skipped when `qemu-img` is absent (a warning is shown). Step 10
582
+ The wizard requires `qemu-img` and refuses before any prompts or image downloads
583
+ when it is absent, showing the install remedy. For a raw x86 boot without `qemu-img`,
584
+ use `quickchr add --arch x86 --boot-disk-format raw` to create a stopped machine,
585
+ then `quickchr start <name>` with the returned name to boot it. Step 10
517
586
  only appears when the platform supports networks beyond user-mode (macOS or
518
587
  Linux). The provisioning gate (step 11) only appears for RouterOS ≥ 7.20.8;
519
588
  older versions show an upgrade prompt.
@@ -597,7 +666,7 @@ default output form when writing step outputs directly.
597
666
  process that wrote it is alive (an extraction: for 30 minutes after its last
598
667
  write). `prune` removes stale ones before anything else.
599
668
 
600
- `cache list --json` prints one object per entry, including `kind` (`image`, `image-zip`,
669
+ `cache list --json` prints an array of entry objects, including `kind` (`image`, `image-zip`,
601
670
  `packages` or `partial`) and `paths` (every file deleting the entry removes).
602
671
  Files in the cache that quickchr did not write are reported as "Other files"
603
672
  and never deleted. `prune` and `clear` never delete an in-use entry.
@@ -690,7 +759,7 @@ mutate any machine's `machine.json`.
690
759
 
691
760
  | Env var | Effect |
692
761
  |---|---|
693
- | `QUICKCHR_DATA_DIR` | Override the data root (defaults to `$XDG_DATA_HOME/quickchr` or platform equivalent — see §8) |
762
+ | `QUICKCHR_DATA_DIR` | Override the data root (defaults to `~/.local/share/quickchr` on POSIX or the Windows path — see §8) |
694
763
  | `QUICKCHR_NO_PROMPT` | Force non-interactive mode (skips all wizards) |
695
764
  | `QUICKCHR_DEBUG` | Emit `[debug]` lines from the progress logger |
696
765
  | `QUICKCHR_INTEGRATION` | Required to run `test/integration/` |
@@ -814,7 +883,7 @@ throws `INVALID_NAME` for a name that is not a usable path segment.
814
883
  |---|---|---|
815
884
  | Identity | `name`, `state`, `ports`, `restUrl`, `sshPort`, `portBase` | Static metadata persisted in `machine.json` |
816
885
  | Capture | `captureInterface`, `hostGatewayIp` | Platform-correct values for TZSP capture (`tshark -i …`) and the guest→host address any UDP emitter targets |
817
- | Lifecycle | `waitForBoot()`, `waitFor(fn, ms?)`, `stop()`, `shutdown(opts?)`, `remove()`, `clean()`, `destroy()` | Boot readiness, custom polling, power-cut or guest-shutdown teardown |
886
+ | Lifecycle | `waitForBoot()`, `waitFor(fn, ms?)`, `stop()`, `shutdown(opts?)`, `continue(opts?)`, `remove()`, `clean()`, `destroy()` | Boot readiness, custom polling, power-cut or guest-shutdown teardown, frozen resume |
818
887
  | Comms | `rest()`, `exec()`, `monitor()`, `serial()`, `qga()` | REST, RouterOS CLI, QEMU monitor, serial console, QGA (x86 only) |
819
888
  | Provisioning | `license()`, `setDeviceMode()`, `availablePackages()`, `installPackage()` | Post-boot configuration |
820
889
  | Files | `upload()`, `download()` | SCP transfer to/from the CHR |
@@ -836,6 +905,7 @@ interface ChrInstance {
836
905
  waitFor(condition: () => Promise<boolean>, timeoutMs?: number): Promise<boolean>;
837
906
  stop(): Promise<void>; // power cut: SIGTERM, then SIGKILL
838
907
  shutdown(opts?: ShutdownOptions): Promise<ShutdownResult>; // /system/shutdown, bounded fallback
908
+ continue(opts?: ContinueOptions): Promise<void>; // resume a frozen start, REST-ready by default
839
909
  remove(): Promise<void>;
840
910
  clean(): Promise<void>;
841
911
  destroy(): Promise<void>; // stop + remove
@@ -922,6 +992,8 @@ redact before logging or attaching to bug reports.
922
992
  | `installDeps` | boolean | — | **Deprecated, ignored.** Never installed anything; run `quickchr doctor` for install hints |
923
993
  | `dryRun` | boolean | `false` | Print QEMU command and exit |
924
994
  | `timeoutExtra` | number | `0` | Extra **milliseconds** added to boot timeout |
995
+ | `qemuArgs` | string[] | — | Extra QEMU argv elements, one entry each, appended verbatim last. Recorded by `add()`; a `start()` value replaces stored args for that boot only |
996
+ | `frozen` | boolean | `false` | Boot frozen at reset with a gdbstub; resume with `continue()`. Refused with provisioning or `background: false` |
925
997
  | `onProgress` | `(msg) => void` | `console.log` | `[debug]` lines only when `QUICKCHR_DEBUG=1` |
926
998
 
927
999
  Provisioning fields trigger a version check at the top of `start()` /
@@ -1257,10 +1329,10 @@ restrictions, half-open SLiRP under TCG), see `docs/networking.md`.
1257
1329
 
1258
1330
  ## 8. Storage layout
1259
1331
 
1260
- ### macOS / Linux (XDG)
1332
+ ### macOS / Linux
1261
1333
 
1262
1334
  ```text
1263
- $XDG_DATA_HOME/quickchr/ (defaults to ~/.local/share/quickchr/)
1335
+ ~/.local/share/quickchr/
1264
1336
  ├── .allocate.lock/ held while a new machine picks its ports/MACs
1265
1337
  ├── cache/ Downloaded RouterOS images
1266
1338
  │ ├── chr-7.22.1.img.zip
@@ -1283,8 +1355,8 @@ $XDG_DATA_HOME/quickchr/ (defaults to ~/.local/share/quickchr/)
1283
1355
  └── <name>.json
1284
1356
  ```
1285
1357
 
1286
- `$XDG_DATA_HOME` defaults to `~/.local/share/quickchr/`. Override the
1287
- whole tree with `QUICKCHR_DATA_DIR=/some/path`.
1358
+ The data root is `~/.local/share/quickchr/`; `XDG_DATA_HOME` does not
1359
+ change it. Override the whole tree with `QUICKCHR_DATA_DIR=/some/path`.
1288
1360
 
1289
1361
  **Global settings** live separately, under the XDG **config** tier, not this
1290
1362
  data tree: `~/.config/quickchr/quickchr.env` (dotenv-style). See §3's
@@ -1581,12 +1653,16 @@ ceiling.
1581
1653
 
1582
1654
  ### Boot disk
1583
1655
 
1584
- Default format is **raw** (the image MikroTik ships). `--boot-size`
1585
- auto-converts to **qcow2** before the first boot and resizes to the
1586
- requested size. Requires `qemu-img` on the host.
1656
+ The CLI and wizard default to **qcow2**, converted from the raw MikroTik
1657
+ image before first boot. Creating qcow2 requires `qemu-img` even without resize.
1658
+ `--boot-size` resizes qcow2 to the requested size; explicitly selecting raw
1659
+ with a boot size is refused. Without `--boot-size`, qcow2 keeps the original
1660
+ image size and enables snapshots.
1587
1661
 
1588
- `--boot-disk-format=qcow2` (without `--boot-size`) just keeps the
1589
- default size but enables snapshots.
1662
+ Library callers default to **raw** unless `bootSize` implies qcow2. CLI users
1663
+ can select raw explicitly with `--boot-disk-format raw`. On x86, this permits
1664
+ creation without `qemu-img` when no resize or extra disks are requested. ARM64
1665
+ also needs `qemu-img` for its writable UEFI firmware disk.
1590
1666
 
1591
1667
  ### Extra disks
1592
1668
 
package/README.md CHANGED
@@ -1,11 +1,5 @@
1
1
  # quickchr
2
2
 
3
- > **Experimental** — quickchr is under active development. Most testing has been on
4
- > macOS (Apple Silicon + Intel), with CI on Linux (x86_64 + aarch64). Windows support
5
- > is early-stage: unit tests pass on `windows-latest` CI, and a weekly sweep boots a real
6
- > CHR on Windows (TCG, anchor smoke subset only). Expect rough edges on Windows — bug
7
- > reports welcome.
8
-
9
3
  CLI and library to download, launch, and manage MikroTik CHR virtual machines via QEMU.
10
4
 
11
5
  ## Quick Start
@@ -14,7 +8,7 @@ CLI and library to download, launch, and manage MikroTik CHR virtual machines vi
14
8
 
15
9
  - [Bun](https://bun.sh) runtime
16
10
  - [QEMU](https://www.qemu.org) with `qemu-system-x86_64` and/or `qemu-system-aarch64`
17
- - `qemu-img` for disk resize and extra-disk features (`--boot-size`, `--add-disk`, `quickchr disk`)
11
+ - `qemu-img` for default qcow2 creation, the setup wizard, disk resize, extra disks, and snapshots
18
12
  - UEFI firmware (edk2) for arm64 CHR
19
13
 
20
14
  Install QEMU:
@@ -60,7 +54,7 @@ bun add @tikoci/quickchr
60
54
  ### Prerelease QA (0.5.1)
61
55
 
62
56
  0.5.1 is published to npm `next` first; the unqualified install above uses `latest`,
63
- and 0.5.1 is promoted there once QA passes. After 0.5.1 is published, pin it for
57
+ and 0.5.1 is promoted there once QA passes. Pin this published candidate for
64
58
  reproducible CLI or library QA:
65
59
 
66
60
  ```bash
@@ -77,7 +71,8 @@ The [0.5.0 notes](./CHANGELOG.md#050--2026-10-22) cover migration from 0.4.x.
77
71
  ### CLI Usage
78
72
 
79
73
  The easiest way to start is the interactive wizard — it walks through
80
- every option and starts the CHR for you:
74
+ every option and starts the CHR for you. It requires `qemu-img` and reports
75
+ a missing tool before prompting or downloading:
81
76
 
82
77
  ```bash
83
78
  quickchr setup
@@ -201,6 +196,7 @@ reports a forced power cut if the guest does not stop within the grace budget.
201
196
  | `--install-all-packages` | Install all packages from `all_packages.zip` | |
202
197
  | `--add-user <user:pass>` | Create a user after boot | |
203
198
  | `--disable-admin` | Disable the default admin account | |
199
+ | `--secure-login` | Create a managed login; use `--disable-admin` to disable admin | |
204
200
  | `--no-secure-login` | Keep admin with no password (skip managed account creation) | |
205
201
  | `--add-network <spec>` | Add a network NIC (repeatable). Specs: `user`, `shared`, `bridged:<iface>`, `socket::<name>`, `tap:<iface>`. Default: single user NIC. | |
206
202
  | `--no-network` | Start with no NICs (headless) | |
@@ -260,7 +256,8 @@ In **foreground** mode, your terminal becomes the CHR serial console. Use these
260
256
 
261
257
  ### Disk Support
262
258
 
263
- - The CHR boot image starts as raw. Using `--boot-size` converts it to qcow2 and resizes it before first boot.
259
+ - CLI and wizard boot disks default to qcow2, converted from the raw MikroTik image using `qemu-img`. Library callers default to raw unless `bootSize` implies qcow2.
260
+ - `--boot-size` resizes qcow2 before first boot; explicitly combining raw with a boot size is refused.
264
261
  - Extra disks from `--add-disk` are always created as blank qcow2 images.
265
262
  - `quickchr clean <name>` removes any resized boot disk and extra disks, then recreates them from the saved machine config.
266
263
  - `quickchr disk <name>` shows the stored disk layout. If `qemu-img` is installed, it also shows virtual and actual sizes.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tikoci/quickchr",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "CLI and library to download, launch, and manage MikroTik CHR virtual machines via QEMU",
5
5
  "module": "src/index.ts",
6
6
  "main": "src/index.ts",
@@ -15,6 +15,7 @@
15
15
  }
16
16
  },
17
17
  "files": [
18
+ ".bun-version",
18
19
  "src",
19
20
  "README.md",
20
21
  "DESIGN.md",
package/src/cli/flags.ts CHANGED
@@ -20,12 +20,13 @@ const CREATE_FLAGS = [
20
20
  "boot-disk-format", "boot-size", "add-disk",
21
21
  "device-mode", "device-mode-enable", "device-mode-disable",
22
22
  "add-user", "disable-admin", "secure-login",
23
+ "qemu-arg",
23
24
  ] as const;
24
25
 
25
26
  /** `start` also boots, so it owns the run-time flags on top of the create set. */
26
27
  const START_ONLY_FLAGS = [
27
28
  "all", "dry-run", "wait", "background", "bg", "fg", "foreground",
28
- "timeout-extra",
29
+ "timeout-extra", "frozen",
29
30
  "license-level", "license-account", "license-password",
30
31
  ] as const;
31
32
 
@@ -40,6 +41,7 @@ const START_ONLY_FLAGS = [
40
41
  const SET_ONLY_FLAGS = [
41
42
  "name", "license", "level", "account", "password",
42
43
  "device-mode", "device-mode-enable", "device-mode-disable",
44
+ "qemu-arg", "clear-qemu-args",
43
45
  ] as const;
44
46
 
45
47
  export const ADD_FLAGS: readonly string[] = CREATE_FLAGS;
@@ -78,6 +80,7 @@ export const VALUE_FLAGS: ReadonlySet<string> = new Set([
78
80
  "device-mode", "device-mode-enable", "device-mode-disable",
79
81
  "add-user", "timeout-extra",
80
82
  "license-level", "license-account", "license-password",
83
+ "qemu-arg",
81
84
  // exec / qga / console
82
85
  "via", "user", "password", "timeout", "path", "data", "script",
83
86
  // set --license