@tikoci/quickchr 0.5.1 → 0.5.2
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 +1 -0
- package/DESIGN.md +3 -1
- package/MANUAL.md +27 -18
- package/README.md +7 -10
- package/package.json +2 -1
- package/src/cli/index.ts +4 -2
- package/src/cli/wizard.ts +54 -58
- package/src/lib/platform.ts +13 -1
- package/src/lib/quickchr.ts +4 -9
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)
|
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
|
-
|
|
119
|
+
`doctor` reports the tested Bun pin and warns when the running runtime is older.
|
|
120
|
+
|
|
121
|
+

|
|
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
|
|
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 | `
|
|
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`;
|
|
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 |
|
|
@@ -486,7 +488,7 @@ Interactive wizard (`@clack/prompts`) that walks through all machine creation
|
|
|
486
488
|
options and starts the CHR. Triggered by `quickchr setup` or automatically
|
|
487
489
|
when `quickchr` is run with no subcommand on a TTY.
|
|
488
490
|
|
|
489
|
-

|
|
491
|
+

|
|
490
492
|
|
|
491
493
|
**First-run detection**: if no machines exist yet, the wizard opens with a
|
|
492
494
|
welcome message before the first prompt.
|
|
@@ -513,7 +515,10 @@ another machine, view status, or quit.
|
|
|
513
515
|
| 12 | Run mode: background / foreground | background | (default) / `--fg` |
|
|
514
516
|
| 13 | Final confirmation | — | — |
|
|
515
517
|
|
|
516
|
-
|
|
518
|
+
The wizard requires `qemu-img` and refuses before any prompts or image downloads
|
|
519
|
+
when it is absent, showing the install remedy. For a raw x86 boot without `qemu-img`,
|
|
520
|
+
use `quickchr add --arch x86 --boot-disk-format raw` to create a stopped machine,
|
|
521
|
+
then `quickchr start <name>` with the returned name to boot it. Step 10
|
|
517
522
|
only appears when the platform supports networks beyond user-mode (macOS or
|
|
518
523
|
Linux). The provisioning gate (step 11) only appears for RouterOS ≥ 7.20.8;
|
|
519
524
|
older versions show an upgrade prompt.
|
|
@@ -597,7 +602,7 @@ default output form when writing step outputs directly.
|
|
|
597
602
|
process that wrote it is alive (an extraction: for 30 minutes after its last
|
|
598
603
|
write). `prune` removes stale ones before anything else.
|
|
599
604
|
|
|
600
|
-
`cache list --json` prints
|
|
605
|
+
`cache list --json` prints an array of entry objects, including `kind` (`image`, `image-zip`,
|
|
601
606
|
`packages` or `partial`) and `paths` (every file deleting the entry removes).
|
|
602
607
|
Files in the cache that quickchr did not write are reported as "Other files"
|
|
603
608
|
and never deleted. `prune` and `clear` never delete an in-use entry.
|
|
@@ -690,7 +695,7 @@ mutate any machine's `machine.json`.
|
|
|
690
695
|
|
|
691
696
|
| Env var | Effect |
|
|
692
697
|
|---|---|
|
|
693
|
-
| `QUICKCHR_DATA_DIR` | Override the data root (defaults to
|
|
698
|
+
| `QUICKCHR_DATA_DIR` | Override the data root (defaults to `~/.local/share/quickchr` on POSIX or the Windows path — see §8) |
|
|
694
699
|
| `QUICKCHR_NO_PROMPT` | Force non-interactive mode (skips all wizards) |
|
|
695
700
|
| `QUICKCHR_DEBUG` | Emit `[debug]` lines from the progress logger |
|
|
696
701
|
| `QUICKCHR_INTEGRATION` | Required to run `test/integration/` |
|
|
@@ -1257,10 +1262,10 @@ restrictions, half-open SLiRP under TCG), see `docs/networking.md`.
|
|
|
1257
1262
|
|
|
1258
1263
|
## 8. Storage layout
|
|
1259
1264
|
|
|
1260
|
-
### macOS / Linux
|
|
1265
|
+
### macOS / Linux
|
|
1261
1266
|
|
|
1262
1267
|
```text
|
|
1263
|
-
|
|
1268
|
+
~/.local/share/quickchr/
|
|
1264
1269
|
├── .allocate.lock/ held while a new machine picks its ports/MACs
|
|
1265
1270
|
├── cache/ Downloaded RouterOS images
|
|
1266
1271
|
│ ├── chr-7.22.1.img.zip
|
|
@@ -1283,8 +1288,8 @@ $XDG_DATA_HOME/quickchr/ (defaults to ~/.local/share/quickchr/)
|
|
|
1283
1288
|
└── <name>.json
|
|
1284
1289
|
```
|
|
1285
1290
|
|
|
1286
|
-
|
|
1287
|
-
whole tree with `QUICKCHR_DATA_DIR=/some/path`.
|
|
1291
|
+
The data root is `~/.local/share/quickchr/`; `XDG_DATA_HOME` does not
|
|
1292
|
+
change it. Override the whole tree with `QUICKCHR_DATA_DIR=/some/path`.
|
|
1288
1293
|
|
|
1289
1294
|
**Global settings** live separately, under the XDG **config** tier, not this
|
|
1290
1295
|
data tree: `~/.config/quickchr/quickchr.env` (dotenv-style). See §3's
|
|
@@ -1581,12 +1586,16 @@ ceiling.
|
|
|
1581
1586
|
|
|
1582
1587
|
### Boot disk
|
|
1583
1588
|
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1589
|
+
The CLI and wizard default to **qcow2**, converted from the raw MikroTik
|
|
1590
|
+
image before first boot. Creating qcow2 requires `qemu-img` even without resize.
|
|
1591
|
+
`--boot-size` resizes qcow2 to the requested size; explicitly selecting raw
|
|
1592
|
+
with a boot size is refused. Without `--boot-size`, qcow2 keeps the original
|
|
1593
|
+
image size and enables snapshots.
|
|
1587
1594
|
|
|
1588
|
-
|
|
1589
|
-
|
|
1595
|
+
Library callers default to **raw** unless `bootSize` implies qcow2. CLI users
|
|
1596
|
+
can select raw explicitly with `--boot-disk-format raw`. On x86, this permits
|
|
1597
|
+
creation without `qemu-img` when no resize or extra disks are requested. ARM64
|
|
1598
|
+
also needs `qemu-img` for its writable UEFI firmware disk.
|
|
1590
1599
|
|
|
1591
1600
|
### Extra disks
|
|
1592
1601
|
|
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
|
|
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.
|
|
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
|
-
-
|
|
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.
|
|
3
|
+
"version": "0.5.2",
|
|
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/index.ts
CHANGED
|
@@ -3233,7 +3233,7 @@ options still work on older versions when the required host tools are installed.
|
|
|
3233
3233
|
${ROUTEROS6_HELP}
|
|
3234
3234
|
|
|
3235
3235
|
Options:
|
|
3236
|
-
--name <name> Instance name (
|
|
3236
|
+
--name <name> Instance name (auto-generated if omitted)
|
|
3237
3237
|
--version <ver> RouterOS version (e.g., 7.22.1)
|
|
3238
3238
|
--channel <ch> Channel: stable, long-term, testing, development
|
|
3239
3239
|
--arch <arch> Architecture: arm64, x86, auto (default: host native)
|
|
@@ -3257,6 +3257,7 @@ Options:
|
|
|
3257
3257
|
--install-all-packages Install all packages on first boot
|
|
3258
3258
|
--add-user <u:p> Create user on first boot (name:password)
|
|
3259
3259
|
--disable-admin Disable the default admin account on first boot
|
|
3260
|
+
--secure-login Create a managed login; use --disable-admin to disable admin
|
|
3260
3261
|
--no-secure-login Keep admin with no password (skip managed account)
|
|
3261
3262
|
--port-base <port> Starting port number (default: auto-allocated from 9100)
|
|
3262
3263
|
--no-winbox Exclude WinBox port mapping
|
|
@@ -3319,7 +3320,7 @@ ${CENTRS_SEE_ALSO}`);
|
|
|
3319
3320
|
case "start":
|
|
3320
3321
|
console.log(`quickchr start [<name>] [options]
|
|
3321
3322
|
|
|
3322
|
-
<name> Restart
|
|
3323
|
+
<name> Restart a stopped machine; an unknown name creates a fresh machine.
|
|
3323
3324
|
Omit to see a list of stopped machines.
|
|
3324
3325
|
|
|
3325
3326
|
Creation flags below apply when using 'start' to create a new machine in one step.
|
|
@@ -3367,6 +3368,7 @@ Options:
|
|
|
3367
3368
|
btest:9200-9210:2000-2010/udp.
|
|
3368
3369
|
--add-user <u:p> Create user with name:password
|
|
3369
3370
|
--disable-admin Disable the default admin account
|
|
3371
|
+
--secure-login Create a managed login; use --disable-admin to disable admin
|
|
3370
3372
|
--no-secure-login Keep admin with no password (skip managed account)
|
|
3371
3373
|
--port-base <port> Starting port number (default: auto-allocated from 9100)
|
|
3372
3374
|
--no-winbox Exclude WinBox port mapping
|
package/src/cli/wizard.ts
CHANGED
|
@@ -37,6 +37,9 @@ export function resolveUserChoiceOptions(
|
|
|
37
37
|
|
|
38
38
|
/** Run the interactive wizard using @clack/prompts. */
|
|
39
39
|
export async function runWizard(wizardOpts?: { firstRun?: boolean }): Promise<void> {
|
|
40
|
+
const { requireQemuImg } = await import("../lib/platform.ts");
|
|
41
|
+
requireQemuImg();
|
|
42
|
+
|
|
40
43
|
// Dynamic import — only loaded when wizard is actually used
|
|
41
44
|
const clack = await import("@clack/prompts");
|
|
42
45
|
const { QuickCHR } = await import("../lib/quickchr.ts");
|
|
@@ -148,75 +151,68 @@ export async function runWizard(wizardOpts?: { firstRun?: boolean }): Promise<vo
|
|
|
148
151
|
});
|
|
149
152
|
if (clack.isCancel(mem)) { clack.cancel("Cancelled."); process.exit(0); }
|
|
150
153
|
|
|
151
|
-
// 4b. Disk configuration —
|
|
154
|
+
// 4b. Disk configuration — qemu-img was checked before any prompts
|
|
152
155
|
let bootSize: string | undefined;
|
|
153
156
|
let bootDiskFormat: "qcow2" | "raw" = "qcow2";
|
|
154
157
|
const extraDisks: string[] = [];
|
|
155
158
|
|
|
156
|
-
const { findQemuImg, getQemuInstallHint } = await import("../lib/platform.ts");
|
|
157
159
|
const { isValidDiskSize } = await import("../lib/disk.ts");
|
|
158
|
-
const
|
|
160
|
+
const diskFormatChoice = await clack.select({
|
|
161
|
+
message: "Boot disk format:",
|
|
162
|
+
options: [
|
|
163
|
+
{ value: "qcow2", label: "qcow2 (recommended)", hint: "supports snapshots and resize" },
|
|
164
|
+
{ value: "raw", label: "raw", hint: "no resize support" },
|
|
165
|
+
],
|
|
166
|
+
initialValue: "qcow2",
|
|
167
|
+
});
|
|
168
|
+
if (clack.isCancel(diskFormatChoice)) { clack.cancel("Cancelled."); process.exit(0); }
|
|
169
|
+
bootDiskFormat = diskFormatChoice as "qcow2" | "raw";
|
|
159
170
|
|
|
160
|
-
if (
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
message: "Boot disk format:",
|
|
165
|
-
options: [
|
|
166
|
-
{ value: "qcow2", label: "qcow2 (recommended)", hint: "supports snapshots and resize" },
|
|
167
|
-
{ value: "raw", label: "raw", hint: "no resize support" },
|
|
168
|
-
],
|
|
169
|
-
initialValue: "qcow2",
|
|
171
|
+
if (bootDiskFormat === "qcow2") {
|
|
172
|
+
const wantBootResize = await clack.confirm({
|
|
173
|
+
message: "Resize the boot disk? (default is ~128 MB)",
|
|
174
|
+
initialValue: false,
|
|
170
175
|
});
|
|
171
|
-
if (clack.isCancel(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
if (clack.isCancel(wantBootResize)) { clack.cancel("Cancelled."); process.exit(0); }
|
|
177
|
+
|
|
178
|
+
if (wantBootResize) {
|
|
179
|
+
const size = await clack.text({
|
|
180
|
+
message: "Boot disk size (e.g., 512M, 1G, 2G):",
|
|
181
|
+
validate: (v) => {
|
|
182
|
+
if (!isValidDiskSize(v)) return "Size like 512M, 1G, or 2048";
|
|
183
|
+
},
|
|
178
184
|
});
|
|
179
|
-
if (clack.isCancel(
|
|
180
|
-
|
|
181
|
-
if (wantBootResize) {
|
|
182
|
-
const size = await clack.text({
|
|
183
|
-
message: "Boot disk size (e.g., 512M, 1G, 2G):",
|
|
184
|
-
validate: (v) => {
|
|
185
|
-
if (!isValidDiskSize(v)) return "Size like 512M, 1G, or 2048";
|
|
186
|
-
},
|
|
187
|
-
});
|
|
188
|
-
if (clack.isCancel(size)) { clack.cancel("Cancelled."); process.exit(0); }
|
|
189
|
-
bootSize = size.trim();
|
|
190
|
-
}
|
|
191
|
-
} else {
|
|
192
|
-
clack.log.info("Raw boot disk selected — boot resize is disabled.");
|
|
185
|
+
if (clack.isCancel(size)) { clack.cancel("Cancelled."); process.exit(0); }
|
|
186
|
+
bootSize = size.trim();
|
|
193
187
|
}
|
|
188
|
+
} else {
|
|
189
|
+
clack.log.info("Raw boot disk selected — boot resize is disabled.");
|
|
190
|
+
}
|
|
194
191
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
192
|
+
const wantExtraDisks = await clack.confirm({
|
|
193
|
+
message: "Add extra blank disks?",
|
|
194
|
+
initialValue: false,
|
|
195
|
+
});
|
|
196
|
+
if (clack.isCancel(wantExtraDisks)) { clack.cancel("Cancelled."); process.exit(0); }
|
|
197
|
+
|
|
198
|
+
if (wantExtraDisks) {
|
|
199
|
+
let addMore = true;
|
|
200
|
+
while (addMore) {
|
|
201
|
+
const diskSize = await clack.text({
|
|
202
|
+
message: `Extra disk ${extraDisks.length + 1} size (e.g., 64M, 512M, 1G):`,
|
|
203
|
+
validate: (v) => {
|
|
204
|
+
if (!isValidDiskSize(v)) return "Size like 64M, 512M, or 1G";
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
if (clack.isCancel(diskSize)) { clack.cancel("Cancelled."); process.exit(0); }
|
|
208
|
+
extraDisks.push(diskSize.trim());
|
|
212
209
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
210
|
+
const another = await clack.confirm({
|
|
211
|
+
message: "Add another disk?",
|
|
212
|
+
initialValue: false,
|
|
213
|
+
});
|
|
214
|
+
if (clack.isCancel(another)) { clack.cancel("Cancelled."); process.exit(0); }
|
|
215
|
+
addMore = another;
|
|
220
216
|
}
|
|
221
217
|
}
|
|
222
218
|
|
package/src/lib/platform.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
6
|
-
import type { AccelMode, EfiFirmwarePaths, HostInterface, PackageManager, PlatformInfo, SocketVmnetInfo } from "./types.ts";
|
|
6
|
+
import type { AccelMode, EfiFirmwarePaths, HostInterface, PackageManager, PlatformInfo, SocketVmnetInfo, DoctorCheck } from "./types.ts";
|
|
7
7
|
import { QuickCHRError as QError, parseAccelMode } from "./types.ts";
|
|
8
8
|
// settings-file.ts, not settings.ts: the latter's graph (cache → state → network
|
|
9
9
|
// → platform) is a cycle that would load eagerly on every CLI start.
|
|
@@ -590,3 +590,15 @@ export function resolveInterfaceAlias(
|
|
|
590
590
|
`No interface found for alias '${alias}' — run 'quickchr doctor' to see available interfaces`,
|
|
591
591
|
);
|
|
592
592
|
}
|
|
593
|
+
|
|
594
|
+
/** Compare the running Bun against the same pin CI installs. */
|
|
595
|
+
export async function bunRuntimeCheck(version = Bun.version): Promise<DoctorCheck> {
|
|
596
|
+
const pin = (await Bun.file(new URL("../../.bun-version", import.meta.url)).text()).trim();
|
|
597
|
+
// Runtimes predating the Semver API are also older than the tested pin.
|
|
598
|
+
const older = typeof Bun.semver?.order !== "function" || Bun.semver.order(version, pin) < 0;
|
|
599
|
+
return {
|
|
600
|
+
label: "Bun runtime",
|
|
601
|
+
status: older ? "warn" : "ok",
|
|
602
|
+
detail: `bun ${version}; tested with Bun ${pin}${older ? ` — upgrade to ${pin} or newer` : ""}`,
|
|
603
|
+
};
|
|
604
|
+
}
|
package/src/lib/quickchr.ts
CHANGED
|
@@ -30,7 +30,7 @@ import type {
|
|
|
30
30
|
import { QuickCHRError, ARCHES, CHANNELS, SERVICE_IDS, QUICKCHR_DESCRIPTOR_VERSION, HOST_GATEWAY_IP, PORTS_PER_BLOCK } from "./types.ts";
|
|
31
31
|
import { assertValidResourceName, assertPathSafeName, shellQuote } from "./names.ts";
|
|
32
32
|
import packageJson from "../../package.json";
|
|
33
|
-
import { detectPlatform, requireQemu, requireFirmware, getQemuVersion, getQemuInstallHint, isCrossArchEmulation, accelTimeoutFactor, detectAccel, accelNote, resolveAccelOverrideWithSource, accelSourceLabel, findQemuImg, qgaKvmWarning, detectSocketVmnet, isSocketVmnetDaemonRunning, findCommandOnPath } from "./platform.ts";
|
|
33
|
+
import { bunRuntimeCheck, detectPlatform, requireQemu, requireFirmware, getQemuVersion, getQemuInstallHint, isCrossArchEmulation, accelTimeoutFactor, detectAccel, accelNote, resolveAccelOverrideWithSource, accelSourceLabel, findQemuImg, qgaKvmWarning, detectSocketVmnet, isSocketVmnetDaemonRunning, findCommandOnPath } from "./platform.ts";
|
|
34
34
|
import {
|
|
35
35
|
resolveVersion,
|
|
36
36
|
isValidVersion,
|
|
@@ -2997,12 +2997,7 @@ export class QuickCHR {
|
|
|
2997
2997
|
static async doctor(): Promise<DoctorResult> {
|
|
2998
2998
|
const checks: DoctorResult["checks"] = [];
|
|
2999
2999
|
|
|
3000
|
-
|
|
3001
|
-
checks.push({
|
|
3002
|
-
label: "Bun runtime",
|
|
3003
|
-
status: "ok",
|
|
3004
|
-
detail: `bun ${Bun.version}`,
|
|
3005
|
-
});
|
|
3000
|
+
checks.push(await bunRuntimeCheck());
|
|
3006
3001
|
|
|
3007
3002
|
// QEMU for each arch
|
|
3008
3003
|
for (const arch of ARCHES) {
|
|
@@ -3125,7 +3120,7 @@ export class QuickCHR {
|
|
|
3125
3120
|
});
|
|
3126
3121
|
}
|
|
3127
3122
|
|
|
3128
|
-
// qemu-img (
|
|
3123
|
+
// qemu-img (default qcow2 creation and disk features)
|
|
3129
3124
|
const qemuImgPath = findQemuImg();
|
|
3130
3125
|
if (qemuImgPath) {
|
|
3131
3126
|
checks.push({
|
|
@@ -3137,7 +3132,7 @@ export class QuickCHR {
|
|
|
3137
3132
|
checks.push({
|
|
3138
3133
|
label: "qemu-img",
|
|
3139
3134
|
status: "warn",
|
|
3140
|
-
detail: `not found — required for --boot-size and --add-disk (${getQemuInstallHint()})`,
|
|
3135
|
+
detail: `not found — required for default qcow2 creation, setup, --boot-size and --add-disk (${getQemuInstallHint()})`,
|
|
3141
3136
|
});
|
|
3142
3137
|
}
|
|
3143
3138
|
|