@tikoci/quickchr 0.5.0 → 0.5.1
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/DESIGN.md +57 -7
- package/MANUAL.md +124 -26
- package/README.md +20 -2
- package/package.json +2 -2
- package/src/cli/flags.ts +7 -2
- package/src/cli/index.ts +59 -31
- package/src/lib/cache.ts +220 -38
- package/src/lib/disk.ts +90 -33
- package/src/lib/download.ts +3 -2
- package/src/lib/images.ts +34 -15
- package/src/lib/lock.ts +2 -1
- package/src/lib/packages.ts +66 -40
- package/src/lib/quickchr.ts +83 -53
- package/src/lib/rename.ts +41 -0
- package/src/lib/settings.ts +20 -9
- package/src/lib/state.ts +26 -1
- package/src/lib/storage-lease.ts +169 -0
- package/src/lib/storage.ts +223 -66
- package/src/lib/types.ts +13 -3
package/DESIGN.md
CHANGED
|
@@ -144,7 +144,8 @@ Current consumers are all first-party tikoci projects (centrs, donny, restraml),
|
|
|
144
144
|
├── machines/
|
|
145
145
|
│ └── 7.22.1-arm64-1/
|
|
146
146
|
│ ├── machine.json # Config + state
|
|
147
|
-
│ ├── disk.img
|
|
147
|
+
│ ├── boot.qcow2 # Boot disk, qcow2 — or disk.img when raw (see below)
|
|
148
|
+
│ ├── disk<N>.qcow2 # Extra disks
|
|
148
149
|
│ ├── efi-vars.qcow2 # UEFI vars (arm64; qcow2 so savevm works — #31)
|
|
149
150
|
│ ├── monitor.sock # QEMU monitor
|
|
150
151
|
│ ├── serial.sock # Serial console
|
|
@@ -156,12 +157,60 @@ Current consumers are all first-party tikoci projects (centrs, donny, restraml),
|
|
|
156
157
|
└── boot-failure-<machine>-<iso8601>.json
|
|
157
158
|
```
|
|
158
159
|
|
|
160
|
+
**One boot disk per machine.** A raw machine boots `disk.img`, a copy of the cached
|
|
161
|
+
image. A qcow2 machine boots `boot.qcow2`, converted straight from the cached image
|
|
162
|
+
into a `.tmp` file, checked to be a standalone qcow2 (no backing file), then renamed
|
|
163
|
+
into place. Machines created before 0.5.1 also kept the raw `disk.img` the qcow2 came
|
|
164
|
+
from: a full-size copy (128 MiB x86) that nothing reads. Restart no longer requires it
|
|
165
|
+
(#200). `clean` removes it, and nothing else deletes it implicitly (#199). The shared
|
|
166
|
+
cache image stays: `clean` and a failed first boot's rollback rebuild from it.
|
|
167
|
+
|
|
168
|
+
A missing disk is rebuilt only while the machine has never booted. A booted machine's
|
|
169
|
+
disks hold guest state, so a missing `boot.qcow2`, `disk.img` or extra disk is
|
|
170
|
+
`MACHINE_NOT_FOUND`, never a silent rebuild from factory. The way out is to restore the
|
|
171
|
+
file or run `quickchr clean`.
|
|
172
|
+
|
|
173
|
+
**Storage admission (#199).** An operation that writes new data (creating a machine,
|
|
174
|
+
an image or package download) is admitted when **each filesystem it writes to** has
|
|
175
|
+
`estimate + storage-reserve` free there. It replaced a flat 10 GiB floor that refused
|
|
176
|
+
small hosts which had room. The operation's writes are parts with paths (boot disk →
|
|
177
|
+
`machines/`, downloads → the cache it writes to), grouped by `stat().dev`. The estimate
|
|
178
|
+
is a per-stage constant (`STORAGE_ESTIMATES` in `storage.ts`), rounded up from one
|
|
179
|
+
host's measurements and written down there. The reserve (default 2 GiB) absorbs the
|
|
180
|
+
estimate's error and keeps the host usable. Restart is never gated. Virtual disk
|
|
181
|
+
capacity is not charged: a sparse disk's growth is the guest's, after admission.
|
|
182
|
+
|
|
183
|
+
Under pressure, eligible cache entries are evicted, but only from a cache on the short
|
|
184
|
+
filesystem, and only when everything eligible could cover the gap. Free space is then
|
|
185
|
+
**measured again**, in passes, until the measurement admits or nothing more can be
|
|
186
|
+
evicted. Logical sizes are not proof of room (sparse files, APFS clones).
|
|
187
|
+
|
|
188
|
+
**Coordination** (`storage-lease.ts`, after Codex's review of #203 reproduced both
|
|
189
|
+
races). One data-dir lock, held for milliseconds: a check-and-record, or an eviction
|
|
190
|
+
pass. Under it, an operation records a **lease** naming the cache entries it is about
|
|
191
|
+
to read (pins) and the bytes it is about to write, per filesystem. Admission subtracts
|
|
192
|
+
other live leases' bytes from measured free space, so two creators cannot spend the
|
|
193
|
+
same bytes. Eviction skips pinned entries. A pin is recorded before the reader checks
|
|
194
|
+
the entry exists, so an eviction either sees it or has already finished, and then the
|
|
195
|
+
reader fetches the entry again. A create's lease lasts until its `machine.json` is
|
|
196
|
+
saved, which protects the image from then on. A download's lease lasts the download.
|
|
197
|
+
A dead pid's lease is ignored and deleted. A create's own downloads are checked up
|
|
198
|
+
front, so it refuses early, but are reserved only by the download itself, so they are
|
|
199
|
+
not counted twice. Automatic eviction also skips anything used in the last 30
|
|
200
|
+
minutes (a cache hit bumps its mtime). That rule is not what protects a reader; pins
|
|
201
|
+
are. It spares what a finished `cache add` fetched for a later process.
|
|
202
|
+
|
|
203
|
+
The cache cap (`cache-max-size`) is retention, separate from admission. It counts
|
|
204
|
+
images, package archives with their extractions (one entry: the extraction is only a
|
|
205
|
+
hit while its archive exists) and interrupted downloads. Files quickchr did not write
|
|
206
|
+
are reported and never deleted. A failed delete is reported as failed, not freed.
|
|
207
|
+
|
|
159
208
|
**Global settings** live separately, under the XDG **config** tier (not the data tree
|
|
160
209
|
above): `~/.config/quickchr/quickchr.env`, dotenv-style (`QUICKCHR_KEY=value` lines).
|
|
161
210
|
Managed via `quickchr settings get|set|print|reset`. Precedence per key: CLI flag >
|
|
162
|
-
`QUICKCHR_<KEY>` env var > `quickchr.env` > built-in default. The
|
|
163
|
-
`default-channel`, `default-arch`, `accel`, `cache-max-size`, `
|
|
164
|
-
`secure-login`.
|
|
211
|
+
`QUICKCHR_<KEY>` env var > `quickchr.env` > built-in default. The 7 managed keys:
|
|
212
|
+
`default-channel`, `default-arch`, `accel`, `cache-max-size`, `storage-reserve`,
|
|
213
|
+
`timeout-extra`, `secure-login`.
|
|
165
214
|
See MANUAL.md's CLI reference and environment-variables sections for the full surface.
|
|
166
215
|
|
|
167
216
|
## Platform Support
|
|
@@ -229,9 +278,10 @@ with a layered design:
|
|
|
229
278
|
- **`release.yml`** — one-click publish from committed release state: freshness gate
|
|
230
279
|
(no suite re-run — main is kept continuously release-able) + `package.json` version
|
|
231
280
|
with a matching non-empty CHANGELOG section → tag + GitHub Release + `npm publish
|
|
232
|
-
--provenance`
|
|
233
|
-
|
|
234
|
-
|
|
281
|
+
--provenance` under the dispatch-time `npm-tag` (default `next`; promoted to `latest`
|
|
282
|
+
after QA — not derived from the version number; a `latest` release is also tagged
|
|
283
|
+
`next`, since there is no separately maintained pre-release line and `next` must not
|
|
284
|
+
point at something older than `latest`). CI never bumps versions or pushes
|
|
235
285
|
to protected `main`.
|
|
236
286
|
- **`ros-versions.yml`** — daily: new RouterOS versions (per channel) with no linux-x86
|
|
237
287
|
record in `ci-data/tested-versions.json` ride the `routeros-targets` matrix of a
|
package/MANUAL.md
CHANGED
|
@@ -72,31 +72,40 @@ by the user (or their AI agent) using `quickchr` as a building block.
|
|
|
72
72
|
|
|
73
73
|
### Install quickchr
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
odd/even policy. Not yet on npm.
|
|
75
|
+
Install the stable npm channel:
|
|
77
76
|
|
|
78
77
|
```bash
|
|
79
|
-
|
|
80
|
-
bun add
|
|
78
|
+
bun install -g @tikoci/quickchr
|
|
79
|
+
bun add @tikoci/quickchr
|
|
80
|
+
```
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
For prerelease QA, use `@next` for the current candidate or, after publication,
|
|
83
|
+
`@0.5.1` to pin this candidate. A release is published to `next` first and promoted
|
|
84
|
+
to `latest` once QA passes; `next` is never older than `latest`. The quickchr package
|
|
85
|
+
channel is independent of `--channel`, which selects a RouterOS firmware channel.
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
bun install -g @tikoci/quickchr@0.5.1
|
|
89
|
+
bun add @tikoci/quickchr@0.5.1
|
|
90
|
+
quickchr --version
|
|
87
91
|
```
|
|
88
92
|
|
|
89
|
-
|
|
93
|
+
To run an unpublished checkout:
|
|
90
94
|
|
|
91
95
|
```bash
|
|
92
|
-
|
|
96
|
+
git clone https://github.com/tikoci/quickchr
|
|
97
|
+
cd quickchr
|
|
98
|
+
bun install
|
|
99
|
+
bun run dev -- doctor
|
|
93
100
|
```
|
|
94
101
|
|
|
95
102
|
### `quickchr doctor`
|
|
96
103
|
|
|
97
104
|
Reports prerequisite status, accelerator availability, image cache
|
|
98
105
|
size, machine count, free disk space, and shell-completion install
|
|
99
|
-
state.
|
|
106
|
+
state. The disk check measures the data dir's filesystem and warns when free
|
|
107
|
+
space is under `storage-reserve` + 2 GiB. Exit code is `0` when no checks return
|
|
108
|
+
`error` (warnings are OK).
|
|
100
109
|
|
|
101
110
|
```bash
|
|
102
111
|
quickchr doctor # human-readable
|
|
@@ -150,6 +159,7 @@ directory, downloads the RouterOS image (cached), and writes
|
|
|
150
159
|
| `--device-mode <m>` | — | `rose`, `advanced`, `basic`, `home`, `auto`, `skip`, ≥ 7.20.8 |
|
|
151
160
|
| `--device-mode-enable <f,...>` | — | Repeatable, comma-separated |
|
|
152
161
|
| `--device-mode-disable <f,...>` | — | Repeatable, comma-separated |
|
|
162
|
+
| `--no-device-mode` | — | Skip device-mode provisioning entirely |
|
|
153
163
|
| `--add-network <spec>` | `user` | See §7. Repeatable. |
|
|
154
164
|
| `--no-network` | false | Headless, no NICs |
|
|
155
165
|
| `--no-winbox` | false | Exclude WinBox port |
|
|
@@ -168,15 +178,19 @@ stdio to your terminal (`--fg`). Accepts the same provisioning flags as `add`.
|
|
|
168
178
|
QEMU runs detached in its own session either way, so it survives a signal to the
|
|
169
179
|
shell that started it.
|
|
170
180
|
|
|
181
|
+
When `<name>` matches no machine (a typo, or one removed earlier), `start` creates a
|
|
182
|
+
factory CHR under that name and says so: `<name> created and started`, or
|
|
183
|
+
`created — not waiting for RouterOS` with `--no-wait`. A machine that already
|
|
184
|
+
existed, including one made by `add`, prints `started`.
|
|
185
|
+
|
|
171
186
|
Additional flags:
|
|
172
187
|
|
|
173
188
|
| Flag | Notes |
|
|
174
189
|
|---|---|
|
|
175
|
-
| `--all` | Start every stopped machine in background; accepts readiness, acceleration, timeout and secure-login options only. Other options are refused rather than ignored |
|
|
190
|
+
| `--all` | Start every stopped machine in background; accepts readiness, acceleration, timeout and secure-login options only. Other options are refused rather than ignored. `--secure-login` is resolved per machine: one that has never booted creates the managed login; one that already booted accepts it as a no-op only if it already has one, and is refused otherwise. With `--no-wait`, a never-booted machine refuses it (provisioning needs the wait). Machines start one at a time, so a refusal stops the batch after the earlier machines have started |
|
|
176
191
|
| `--no-wait` | Return as soon as QEMU is running (past the 1.5 s early-exit check), with state and ports persisted; do not wait for RouterOS. Refused with any provisioning flag, including first-boot options `add` recorded, and with `--fg` |
|
|
177
192
|
| `--fg` / `--foreground` (alias `--no-bg`, `--no-background`) | Serial console on stdio |
|
|
178
193
|
| `--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` |
|
|
179
|
-
| `--install-deps` | Auto-install missing host deps (calls package manager) |
|
|
180
194
|
| `--timeout-extra <s>` / `-T <s>` | Add extra seconds to computed boot timeout |
|
|
181
195
|
| `--dry-run` | Print resolved options and QEMU command, exit |
|
|
182
196
|
|
|
@@ -394,7 +408,8 @@ the Library API reference.
|
|
|
394
408
|
#### `exec <name> <command> [--via=auto|rest|qga|console|ssh] [--user u] [--password p] [--timeout=ms]`
|
|
395
409
|
|
|
396
410
|
Run a RouterOS CLI command. See §12 for transport details and JSON
|
|
397
|
-
output trick.
|
|
411
|
+
output trick. Exit 0 means the command was delivered, not that RouterOS
|
|
412
|
+
accepted it — see §12 "Exit status" for the pass/fail route.
|
|
398
413
|
|
|
399
414
|
#### `console <name>`
|
|
400
415
|
|
|
@@ -567,6 +582,64 @@ appended directly to `$GITHUB_OUTPUT`.
|
|
|
567
582
|
`--json` intentionally emits one JSON object for programmatic parsing; use the
|
|
568
583
|
default output form when writing step outputs directly.
|
|
569
584
|
|
|
585
|
+
`cache list [--json]` shows each cache entry with its version, arch, size, age
|
|
586
|
+
(time since it was cached or last used) and whether it is in use. Entries are:
|
|
587
|
+
|
|
588
|
+
- **images** (`chr-<ver>[-arm64].img`, and a `.img.zip` not yet extracted). An
|
|
589
|
+
image is **in use** while any machine, running or stopped, was created from
|
|
590
|
+
that version and arch: `clean` and a failed first boot rebuild from it.
|
|
591
|
+
- **package archives** (`packages-<arch>-<ver>`): the `all_packages` ZIP and its
|
|
592
|
+
extraction, sized and deleted together, since the extraction is only a cache
|
|
593
|
+
hit while its ZIP is there. In use while a machine that asks for packages has
|
|
594
|
+
not finished its first boot.
|
|
595
|
+
- **partial** entries: an interrupted download (`*.<pid>-<id>.part`) or
|
|
596
|
+
extraction (`.quickchr-image-*`, `.quickchr-packages-*`). In use while the
|
|
597
|
+
process that wrote it is alive (an extraction: for 30 minutes after its last
|
|
598
|
+
write). `prune` removes stale ones before anything else.
|
|
599
|
+
|
|
600
|
+
`cache list --json` prints one object per entry, including `kind` (`image`, `image-zip`,
|
|
601
|
+
`packages` or `partial`) and `paths` (every file deleting the entry removes).
|
|
602
|
+
Files in the cache that quickchr did not write are reported as "Other files"
|
|
603
|
+
and never deleted. `prune` and `clear` never delete an in-use entry.
|
|
604
|
+
|
|
605
|
+
```bash
|
|
606
|
+
quickchr cache list
|
|
607
|
+
quickchr cache prune --older-than 7.22 --dry-run # versions below 7.22
|
|
608
|
+
quickchr cache prune --max-age 30d # not used for more than 30 days
|
|
609
|
+
quickchr cache prune --max-size 2G # oldest versions first, until <= 2 GiB
|
|
610
|
+
quickchr cache clear --all # every entry not in use
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
`prune` needs at least one of `--older-than <ver>`, `--max-age <dur>` or
|
|
614
|
+
`--max-size <size>`, and applies them in that order when combined. `--max-size`
|
|
615
|
+
evicts the oldest RouterOS version first (oldest cache time breaks ties) until
|
|
616
|
+
the total, in-use entries included, fits. Durations are `<N>(s|m|h|d|w)`; sizes
|
|
617
|
+
are `<N>(K|M|G|T)` with an optional `i`/`B`, read as IEC binary. `--dry-run`
|
|
618
|
+
lists what `prune` or `clear` would delete without deleting it. `clear` refuses
|
|
619
|
+
without `--all`. A delete that fails is listed on stderr, is not counted as
|
|
620
|
+
freed, and makes the command exit 1. A cache hit counts as a use, so `--max-age`
|
|
621
|
+
measures time since an entry was last used.
|
|
622
|
+
|
|
623
|
+
Separately, a successful `start` prunes to the `cache-max-size` setting (default
|
|
624
|
+
`2G`; see `settings` below). That automatic prune, and the one below, skip any
|
|
625
|
+
entry used in the last 30 minutes, which another create may be reading.
|
|
626
|
+
|
|
627
|
+
**Disk space.** Creating a machine (`add`, or `start` with a new name) and
|
|
628
|
+
filling the cache (an image or package download) are admitted when each
|
|
629
|
+
filesystem the operation writes to has room for what it writes there plus the
|
|
630
|
+
`storage-reserve` setting (default `2G`), less what other quickchr operations
|
|
631
|
+
in progress have already claimed. The estimate is the boot disk
|
|
632
|
+
(192 MiB), plus an image download (256 MiB) and a package download (192 MiB)
|
|
633
|
+
when those are not cached yet, so a cold create needs about 2.6 GiB free and a
|
|
634
|
+
warm one about 2.2 GiB. When there is not enough, quickchr first evicts what the
|
|
635
|
+
cache can spare on that filesystem (oldest version first; never entries a
|
|
636
|
+
machine or an operation in progress needs, nor recently used ones), measures
|
|
637
|
+
again until there is room or nothing more can go, and only then refuses with `INSUFFICIENT_DISK_SPACE`,
|
|
638
|
+
naming the free space, what it needed, what it evicted and what it kept.
|
|
639
|
+
Restarting an existing machine, `stop`, `remove` and read-only commands are
|
|
640
|
+
never gated. The estimate covers creation, not a guest that later fills its
|
|
641
|
+
disk: extra disks and `--boot-size` are sparse and grow as the guest writes.
|
|
642
|
+
|
|
570
643
|
#### `settings [print|get|set|reset] [key] [value] [--json]`
|
|
571
644
|
|
|
572
645
|
Manage quickchr's own global preferences — repeated flags/defaults that
|
|
@@ -583,6 +656,7 @@ Precedence per key (highest wins): CLI flag > `QUICKCHR_<KEY>` env var >
|
|
|
583
656
|
| `default-arch` | `add`/`start`'s `--arch`, wizard's arch prompt | `arm64`\|`x86`\|`auto` | `auto` (host native) |
|
|
584
657
|
| `accel` | `add`/`start`'s `--accel`; every `detectAccel()` call in the run | `auto`\|`tcg`\|`hvf`\|`kvm` | `auto` (detect) |
|
|
585
658
|
| `cache-max-size` | Auto-prune cap applied after each successful `start` | size string (e.g. `2G`, `512M`) | `2G` |
|
|
659
|
+
| `storage-reserve` | Free space creating a machine or filling the cache must leave, beyond what it writes | size string | `2G` |
|
|
586
660
|
| `timeout-extra` | `start`'s `--timeout-extra`/`-T` when omitted | non-negative integer seconds | `0` |
|
|
587
661
|
| `secure-login` | `add`/`start`'s `--secure-login` when neither `--secure-login` nor `--no-secure-login` is passed | boolean | `unset` (reported as `(unset)` — see note) |
|
|
588
662
|
|
|
@@ -601,7 +675,7 @@ the wizard's own recommended "managed login" highlighted). Giving it a real
|
|
|
601
675
|
the setting resolves to exactly `true`.
|
|
602
676
|
|
|
603
677
|
```bash
|
|
604
|
-
quickchr settings print # all
|
|
678
|
+
quickchr settings print # all 7 keys, resolved value + source
|
|
605
679
|
quickchr settings set default-channel testing # write one key to quickchr.env
|
|
606
680
|
quickchr settings get default-arch # print one key's resolved value
|
|
607
681
|
quickchr settings reset default-channel # delete one managed line
|
|
@@ -609,7 +683,7 @@ quickchr settings reset # clear all managed lines
|
|
|
609
683
|
```
|
|
610
684
|
|
|
611
685
|
Credentials (`--add-user`, `MIKROTIK_WEB_ACCOUNT`/`MIKROTIK_WEB_PASSWORD`) are
|
|
612
|
-
never stored here — `settings` only manages the
|
|
686
|
+
never stored here — `settings` only manages the 7 keys above, and does not
|
|
613
687
|
mutate any machine's `machine.json`.
|
|
614
688
|
|
|
615
689
|
### Global behavior
|
|
@@ -824,14 +898,14 @@ redact before logging or attaching to bug reports.
|
|
|
824
898
|
| Field | Type | Default | Notes |
|
|
825
899
|
|---|---|---|---|
|
|
826
900
|
| `name` | string | auto | Must not start with `-` |
|
|
827
|
-
| `version` | string | — |
|
|
901
|
+
| `version` | string | — | Wins over `channel`: passing both warns and uses `version` |
|
|
828
902
|
| `channel` | Channel | `"stable"` | |
|
|
829
903
|
| `arch` | `"arm64" \| "x86"` | host | |
|
|
830
904
|
| `cpu` | number | `1` | |
|
|
831
905
|
| `mem` | number | `512` (1024 cross-arch TCG) | MiB |
|
|
832
906
|
| `background` | boolean | `true` | Where the serial console goes — not whether `start()` waits |
|
|
833
907
|
| `wait` | boolean | `true` | `false` resolves once QEMU is running and leaves readiness to the caller (`instance.waitForBoot()`); refused with provisioning or `background: false` (`INVALID_ARGUMENT`) |
|
|
834
|
-
| `bootDiskFormat` | `"raw" \| "qcow2"` | `"raw"` (auto-`qcow2` if `bootSize`) | |
|
|
908
|
+
| `bootDiskFormat` | `"raw" \| "qcow2"` | `"raw"` (auto-`qcow2` if `bootSize`) | The CLI always passes a format, defaulting to `qcow2`; the `"raw"` default applies to library callers only |
|
|
835
909
|
| `bootSize` | string | — | e.g., `"512M"`, `"2G"`. Needs `qemu-img` |
|
|
836
910
|
| `extraDisks` | string[] | `[]` | Each entry is a size string |
|
|
837
911
|
| `networks` | NetworkSpecifier[] | `["user"]` | See §7 |
|
|
@@ -845,7 +919,7 @@ redact before logging or attaching to bug reports.
|
|
|
845
919
|
| `secureLogin` | boolean | `false` | **`true` is the explicit trigger** for managed-account creation |
|
|
846
920
|
| `license` | `LicenseInput` | — | `"p1"` / `"p10"` / `"unlimited"` or `LicenseOptions` |
|
|
847
921
|
| `deviceMode` | `DeviceModeOptions` | — | `{mode?, enable?[], disable?[]}` |
|
|
848
|
-
| `installDeps` | boolean | — |
|
|
922
|
+
| `installDeps` | boolean | — | **Deprecated, ignored.** Never installed anything; run `quickchr doctor` for install hints |
|
|
849
923
|
| `dryRun` | boolean | `false` | Print QEMU command and exit |
|
|
850
924
|
| `timeoutExtra` | number | `0` | Extra **milliseconds** added to boot timeout |
|
|
851
925
|
| `onProgress` | `(msg) => void` | `console.log` | `[debug]` lines only when `QUICKCHR_DEBUG=1` |
|
|
@@ -1195,8 +1269,8 @@ $XDG_DATA_HOME/quickchr/ (defaults to ~/.local/share/quickchr/)
|
|
|
1195
1269
|
├── machines/
|
|
1196
1270
|
│ └── <name>/
|
|
1197
1271
|
│ ├── machine.json Persisted MachineState
|
|
1198
|
-
│ ├──
|
|
1199
|
-
│ ├──
|
|
1272
|
+
│ ├── boot.qcow2 | disk.img boot disk: qcow2 (CLI default) or raw — one, not both
|
|
1273
|
+
│ ├── disk<N>.qcow2 extra disks
|
|
1200
1274
|
│ ├── efi-vars.fd arm64 only (UEFI vars)
|
|
1201
1275
|
│ ├── monitor.sock QEMU monitor
|
|
1202
1276
|
│ ├── serial.sock serial console
|
|
@@ -1224,7 +1298,7 @@ var list.
|
|
|
1224
1298
|
├── cache\
|
|
1225
1299
|
├── machines\<name>\
|
|
1226
1300
|
│ ├── machine.json
|
|
1227
|
-
│ ├── disk.img
|
|
1301
|
+
│ ├── boot.qcow2 | disk.img
|
|
1228
1302
|
│ ├── monitor (named pipe handle, no on-disk file)
|
|
1229
1303
|
│ └── …
|
|
1230
1304
|
└── …
|
|
@@ -1370,7 +1444,7 @@ managed via `quickchr settings get|set|print|reset` (§3, Meta). Atomic writes
|
|
|
1370
1444
|
preserve every other line (comments, blank lines, foreign vars) byte-for-byte.
|
|
1371
1445
|
`reset <key>` deletes the line entirely rather than blanking it, since a blank
|
|
1372
1446
|
`KEY=` line is still "set" to a bash-sourced file. Never stores credentials —
|
|
1373
|
-
the
|
|
1447
|
+
the 7 managed keys are all structural preferences, not secrets.
|
|
1374
1448
|
|
|
1375
1449
|
### Environment variables consumed
|
|
1376
1450
|
|
|
@@ -1387,6 +1461,7 @@ the 6 managed keys are all structural preferences, not secrets.
|
|
|
1387
1461
|
| `QUICKCHR_DEFAULT_ARCH` | `settings.ts` — env tier for the `default-arch` setting |
|
|
1388
1462
|
| `QUICKCHR_ACCEL` | `settings.ts` / `platform.ts` — env tier for the `accel` setting; forces `-accel` and bypasses detection |
|
|
1389
1463
|
| `QUICKCHR_CACHE_MAX_SIZE` | `settings.ts` — env tier for the `cache-max-size` setting |
|
|
1464
|
+
| `QUICKCHR_STORAGE_RESERVE` | `settings.ts` — env tier for the `storage-reserve` setting |
|
|
1390
1465
|
| `QUICKCHR_SHUTDOWN_TIMEOUT` | CLI `shutdown` grace budget in positive integer seconds; default 10, overridden by `--timeout` |
|
|
1391
1466
|
| `QUICKCHR_TIMEOUT_EXTRA` | `settings.ts` — env tier for the `timeout-extra` setting |
|
|
1392
1467
|
| `QUICKCHR_SECURE_LOGIN` | `settings.ts` — env tier for the `secure-login` setting |
|
|
@@ -1436,6 +1511,29 @@ funnel through the same transport selector.
|
|
|
1436
1511
|
> (see [`docs/centrs-interface.md`](./docs/centrs-interface.md)) — it never reads
|
|
1437
1512
|
> `machine.json`, and neither should you.
|
|
1438
1513
|
|
|
1514
|
+
### Exit status
|
|
1515
|
+
|
|
1516
|
+
`quickchr exec` exits 0 when the command was **delivered**, not when RouterOS
|
|
1517
|
+
accepted it. `/rest/execute` returns HTTP 200 with the rejection text in the same
|
|
1518
|
+
envelope as a success, so a refused command looks like this:
|
|
1519
|
+
|
|
1520
|
+
```console
|
|
1521
|
+
$ quickchr exec my-chr "/user/add name=-2 group=read password=x"; echo "rc=$?"
|
|
1522
|
+
failure: invalid user name (/user/add; line 1)
|
|
1523
|
+
rc=0
|
|
1524
|
+
```
|
|
1525
|
+
|
|
1526
|
+
The library is the same: `chr.exec()` resolves, and `ExecResult` has no failure
|
|
1527
|
+
field. quickchr does not pattern-match RouterOS error text, whose wording changes
|
|
1528
|
+
between versions (#142). When a script needs a pass/fail exit code, run the
|
|
1529
|
+
command through centrs, which validates it first and exits non-zero on a rejection.
|
|
1530
|
+
A write command needs `--yes` outside a TTY; without it centrs refuses with
|
|
1531
|
+
`usage/confirmation-required` (also exit 1) and sends nothing:
|
|
1532
|
+
|
|
1533
|
+
```bash
|
|
1534
|
+
centrs execute --quickchr my-chr --yes "/user/add name=-2 group=read password=x"
|
|
1535
|
+
```
|
|
1536
|
+
|
|
1439
1537
|
### Transports
|
|
1440
1538
|
|
|
1441
1539
|
| `--via` | Source | Notes |
|
|
@@ -1605,7 +1703,7 @@ present, then exits `1`.
|
|
|
1605
1703
|
| `DOWNLOAD_FAILED` | image fetch failed | retry; check connectivity to MikroTik download server |
|
|
1606
1704
|
| `INVALID_VERSION` / `INVALID_ARCH` / `INVALID_NAME` / `INVALID_DISK_SIZE` | argument parse | fix the value |
|
|
1607
1705
|
| `MACHINE_EXISTS` | name already in `machines/` | choose another name or `QuickCHR.get(name)` |
|
|
1608
|
-
| `MACHINE_NOT_FOUND` | name not in `machines
|
|
1706
|
+
| `MACHINE_NOT_FOUND` | name not in `machines/`, or a booted machine's disk file is missing | `quickchr list`; for a missing disk, restore it or `quickchr clean <name>` |
|
|
1609
1707
|
| `MACHINE_RUNNING` / `MACHINE_STOPPED` | wrong lifecycle state | `start` or `stop` first |
|
|
1610
1708
|
| `MACHINE_LOCKED` | another operation holds the machine (the message names it, its pid and age) | wait for it; a `foreground` holder must be quit from its console |
|
|
1611
1709
|
| `EXEC_FAILED` | RouterOS rejected the command (auth, syntax) | check creds, command syntax |
|
|
@@ -1618,7 +1716,7 @@ present, then exits `1`.
|
|
|
1618
1716
|
| `PROVISIONING_VERSION_UNSUPPORTED` | provisioning option on RouterOS < 7.20.8 (including any 6.x) | use `--channel long-term` or pin a `7.20.8+` version |
|
|
1619
1717
|
| `REST_UNAVAILABLE` | REST operation (`exec`, `get`, `rest()`) on a RouterOS 6 machine | use the native API — `centrs … --via native-api --no-validate` |
|
|
1620
1718
|
| `INVALID_ARGUMENT` | a flag combination quickchr will not honour — e.g. `--no-wait` with provisioning | drop one of the two |
|
|
1621
|
-
| `INSUFFICIENT_DISK_SPACE` |
|
|
1719
|
+
| `INSUFFICIENT_DISK_SPACE` | data dir's filesystem has less than the operation's estimate plus `storage-reserve`, even after evicting what the cache could spare | free space, remove machines, lower `storage-reserve`, or move via `QUICKCHR_DATA_DIR` |
|
|
1622
1720
|
| `STATE_ERROR` | snapshot on raw boot disk | recreate with `--boot-disk-format=qcow2` |
|
|
1623
1721
|
| `INVALID_SIZE_STRING` | invalid cache/settings size string | use size syntax like `512M`, `2G`, or `1.5G` |
|
|
1624
1722
|
| `INVALID_SETTING_KEY` | `settings get`/`set`/`reset` received an unrecognized key | `quickchr settings print` to see the managed keys |
|
package/README.md
CHANGED
|
@@ -57,6 +57,23 @@ Library:
|
|
|
57
57
|
bun add @tikoci/quickchr
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
### Prerelease QA (0.5.1)
|
|
61
|
+
|
|
62
|
+
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
|
|
64
|
+
reproducible CLI or library QA:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
bun install -g @tikoci/quickchr@0.5.1
|
|
68
|
+
bun add @tikoci/quickchr@0.5.1
|
|
69
|
+
quickchr --version
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Use `@next` instead of `@0.5.1` to follow later candidates. See the
|
|
73
|
+
[0.5.1 changelog](./CHANGELOG.md#051--2026-10-22) for the changes since 0.5.0,
|
|
74
|
+
including storage admission, cache accounting, and disk preservation fixes.
|
|
75
|
+
The [0.5.0 notes](./CHANGELOG.md#050--2026-10-22) cover migration from 0.4.x.
|
|
76
|
+
|
|
60
77
|
### CLI Usage
|
|
61
78
|
|
|
62
79
|
The easiest way to start is the interactive wizard — it walks through
|
|
@@ -195,6 +212,7 @@ reports a forced power cut if the guest does not stop within the grace budget.
|
|
|
195
212
|
| `--device-mode <m>` | Configure device-mode: rose\|advanced\|basic\|home\|auto\|skip | |
|
|
196
213
|
| `--device-mode-enable <f>` | Set one or more device-mode flags to yes | |
|
|
197
214
|
| `--device-mode-disable <f>` | Set one or more device-mode flags to no | |
|
|
215
|
+
| `--no-device-mode` | Skip device-mode provisioning entirely | |
|
|
198
216
|
| `--port-base <port>` | Starting port number | Auto (9100+) |
|
|
199
217
|
| `--timeout-extra <s>` | Add extra seconds to the auto-computed boot timeout | |
|
|
200
218
|
| `--dry-run` | Print what would run without executing | |
|
|
@@ -238,7 +256,7 @@ In **foreground** mode, your terminal becomes the CHR serial console. Use these
|
|
|
238
256
|
| `Ctrl-A C` | Toggle QEMU monitor (`quit` to force-stop) |
|
|
239
257
|
| `Ctrl-A H` | List all key shortcuts |
|
|
240
258
|
|
|
241
|
-
> **Note:** Background QEMU processes are true OS-level orphans — `quickchr` does not use shell job control (`&`). After the command returns you can close the terminal and QEMU keeps running.
|
|
259
|
+
> **Note:** Background QEMU processes are true OS-level orphans — `quickchr` does not use shell job control (`&`). After the command returns you can close the terminal and QEMU keeps running. `quickchr stop <name>` ends it with a power cut; `quickchr shutdown <name>` asks the guest to shut down first, and still falls back to a power cut if it does not exit in time.
|
|
242
260
|
|
|
243
261
|
### Disk Support
|
|
244
262
|
|
|
@@ -437,7 +455,7 @@ afterAll(async () => {
|
|
|
437
455
|
|----------|-------------|
|
|
438
456
|
| `MIKROTIK_WEB_ACCOUNT` | MikroTik.com account email (for license renewal) |
|
|
439
457
|
| `MIKROTIK_WEB_PASSWORD` | MikroTik.com password (for license renewal) |
|
|
440
|
-
| `QUICKCHR_DEFAULT_CHANNEL`, `QUICKCHR_DEFAULT_ARCH`, `QUICKCHR_CACHE_MAX_SIZE`, `QUICKCHR_TIMEOUT_EXTRA`, `QUICKCHR_SECURE_LOGIN` | Env-tier overrides for the
|
|
458
|
+
| `QUICKCHR_DEFAULT_CHANNEL`, `QUICKCHR_DEFAULT_ARCH`, `QUICKCHR_ACCEL`, `QUICKCHR_CACHE_MAX_SIZE`, `QUICKCHR_STORAGE_RESERVE`, `QUICKCHR_TIMEOUT_EXTRA`, `QUICKCHR_SECURE_LOGIN` | Env-tier overrides for the 7 `quickchr settings` keys — see [MANUAL.md §3 `settings`](./MANUAL.md#3-cli-command-reference) |
|
|
441
459
|
| `QUICKCHR_INTEGRATION` | Set to `1` to run integration tests |
|
|
442
460
|
| `QUICKCHR_TEST_TARGET` | Integration tests only: RouterOS channel (`stable`/`long-term`/`testing`/`development`) or pinned version (e.g. `7.22.1`) to boot. Empty = `stable`. The Integration workflow resolves each `routeros-target` once, then sets this to that exact pin across platforms |
|
|
443
461
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tikoci/quickchr",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
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",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"lint": "biome check . && tsc --noEmit",
|
|
26
26
|
"lint:biome": "biome check .",
|
|
27
27
|
"lint:typecheck": "tsc --noEmit",
|
|
28
|
-
"lint:markdown": "
|
|
28
|
+
"lint:markdown": "markdownlint-cli2 '**/*.md'",
|
|
29
29
|
"lint:cspell": "cspell lint . --no-progress",
|
|
30
30
|
"lint:examples": "bun run scripts/validate-examples.ts",
|
|
31
31
|
"lint:specifiers": "bun run scripts/validate-network-specifiers.ts",
|
package/src/cli/flags.ts
CHANGED
|
@@ -25,7 +25,7 @@ const CREATE_FLAGS = [
|
|
|
25
25
|
/** `start` also boots, so it owns the run-time flags on top of the create set. */
|
|
26
26
|
const START_ONLY_FLAGS = [
|
|
27
27
|
"all", "dry-run", "wait", "background", "bg", "fg", "foreground",
|
|
28
|
-
"
|
|
28
|
+
"timeout-extra",
|
|
29
29
|
"license-level", "license-account", "license-password",
|
|
30
30
|
] as const;
|
|
31
31
|
|
|
@@ -96,10 +96,15 @@ export const VALUE_FLAGS: ReadonlySet<string> = new Set([
|
|
|
96
96
|
* that `--add-network` already expresses in general (#164). Removing them silently would
|
|
97
97
|
* repeat the defect they were removed for: a flag that is accepted and does nothing. A
|
|
98
98
|
* removed name stays known for as long as anyone might still type it, and says what
|
|
99
|
-
* replaced it.
|
|
99
|
+
* replaced it.
|
|
100
|
+
*
|
|
101
|
+
* `--install-deps` is the same defect from the other side: `start` accepted it from
|
|
102
|
+
* the beginning and nothing ever read it (#193). It never installed anything, so the
|
|
103
|
+
* replacement is the command that names what to install. */
|
|
100
104
|
export const REMOVED_FLAGS: ReadonlyMap<string, string> = new Map([
|
|
101
105
|
["vmnet-shared", "--add-network shared"],
|
|
102
106
|
["vmnet-bridge", "--add-network bridged:<iface>"],
|
|
107
|
+
["install-deps", "quickchr doctor (it never installed anything; doctor lists missing host dependencies and how to install them)"],
|
|
103
108
|
]);
|
|
104
109
|
|
|
105
110
|
/** Value-taking flags with a documented `--no-` spelling, where `false` is a real
|