@microck/raft 0.3.0 → 0.5.0

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.
@@ -0,0 +1,198 @@
1
+ ---
2
+ title: Box sizing and capacity
3
+ description: Size boxes for the task, give them their own disks, and read the advisory host capacity estimate.
4
+ ---
5
+
6
+ # Box sizing and capacity
7
+
8
+ <Callout type="info" title="Advisory estimate only">
9
+ Raft enforces no box count or size budget. Create the boxes your task needs, sized for the work. `raft capacity` is planning advice, never a reason to skip a box.
10
+ </Callout>
11
+
12
+ ## Sizing a box
13
+
14
+ Choose CPU, RAM and disk when you run `raft new`:
15
+
16
+ - `--cpu N`: any whole number of CPUs, from `1` up to the host's CPU count (default: `1`). Boxes share host CPUs, so a limit is a ceiling, not a reservation.
17
+ - `--memory SIZE`: any whole number of GiB, such as `3GiB` or `32GiB`, up to the host's RAM (default: `2GiB`). This is the box's cgroup memory limit.
18
+ - `--disk SIZE`: give the box its own disk instead of the shared pool, on hosts that enable own box disks. See [Own box disks](#own-box-disks).
19
+
20
+ A box from the image starts from these defaults, or from the controller's
21
+ [`config.json` defaults](../guides/config-policy.mdx#defaults-for-new) when set.
22
+ A box from a snapshot (`new --from`) or a fork keeps the source's CPU, RAM and
23
+ disk instead of any default, and each flag you pass overrides only its own value.
24
+
25
+ ```sh
26
+ box=$(raft new --cpu 4 --memory 16GiB --disk 40GiB --ttl 3600 --purpose "Large build")
27
+ ```
28
+
29
+ A CPU or RAM request larger than the whole host fails before anything is
30
+ created, because Incus would accept it and quietly give the box less:
31
+
32
+ ```text
33
+ Requested 64 CPUs, but this host has 8
34
+ ```
35
+
36
+ Change CPU and RAM when you resume a stopped box or fork one:
37
+
38
+ ```sh
39
+ raft resume "$box" --ttl 1800 --cpu 2 --memory 4GiB
40
+ copy=$(raft fork "$box" --ttl 1800 --memory 2GiB)
41
+ ```
42
+
43
+ Resume requires a stopped box; fork requires a stopped source. Omitted values
44
+ stay unchanged on resume and inherit the source limits on fork. Live resizing is
45
+ not exposed by the CLI. See [Snapshots and forks](../guides/snapshots-forks.mdx).
46
+
47
+ ## Own box disks
48
+
49
+ By default every box stores its files in the shared 60 GiB Btrfs pool
50
+ `raft-data`. Boxes there grow until the pool is full, and Btrfs quotas cannot
51
+ hold them back: Docker and other tools create nested subvolumes that escape
52
+ Btrfs quotas.
53
+
54
+ `raft new --disk SIZE` gives the box its own disk instead: an LVM thin volume
55
+ of SIZE with an ext4 filesystem in the `raft-disks` pool. Everything the box
56
+ writes, including Docker images, layers and container files, lands in that
57
+ filesystem, so the box gets "No space left on device" at SIZE and cannot touch
58
+ other boxes' space. Raft reserves the whole SIZE in the pool when the box is
59
+ created, so other boxes cannot take it either.
60
+
61
+ Own box disks are opt-in per host. A host deployed without `--disk-pool` has no
62
+ `raft-disks` pool, which is a normal, healthy state: `raft doctor` and
63
+ `raft capacity` say own box disks are not enabled, and `new --disk` fails before
64
+ creating anything with the command that enables them.
65
+
66
+ - **Size**: whole GiB, such as `20GiB`. The development image holds about 5 GiB of files, so give boxes at least `8GiB`; a disk too small for the image fails at creation and leaves nothing behind. Disks of 10 GiB or more start fastest: smaller ones are shrunk after the copy. ext4 keeps a little of the volume for itself, so `df` shows slightly less than SIZE.
67
+ - **Copy-on-write**: a new own-disk box starts as a thin snapshot of the image, and forks, templates (`new --from`) and snapshots of own-disk boxes are thin snapshots of their source. They are created in seconds and share the source's blocks until they write their own.
68
+ - **Reservations**: each own disk, and each snapshot of one, reserves its full size in the pool, even though it shares blocks. A request that would reserve more than the pool holds fails before anything is created. See [Reservation accounting](#reservation-accounting).
69
+ - **Forks, templates and backups**: `fork` and `new --from` keep the source disk, shared or own, with the same size. `new --from BOX/SNAPSHOT --disk SIZE` copies a snapshot onto an own disk of a new size. `recover` puts an own-disk backup back onto an own disk of the same size, and needs the pool on the destination. Resume never changes the disk.
70
+ - **Inspection**: `raft list` reports `disk` as `"shared"` or the size; `raft info` shows it in `expanded_devices.root`; `raft usage` reports the filesystem's space with a `filesystemScope` of `own SIZE box disk`.
71
+
72
+ ### Enabling the `raft-disks` pool
73
+
74
+ Pass `--disk-pool SIZE` to `raft host add`, or to
75
+ `python3 deploy/deploy.py --location LOCATION` on a host that is already
76
+ deployed. The deployer creates `raft-disks` beside `raft-data`: a loop-backed
77
+ LVM thin pool (`lvm.use_thinpool=true`) of SIZE. It allocates the pool's file on
78
+ the host disk in full, so every reservation is backed by real host space, and
79
+ it sets the thin pool to `--discards nopassdown`, so freed blocks return to the
80
+ pool without punching holes back into that file. A fresh host then needs 80 GiB
81
+ plus SIZE free; adding the pool later needs SIZE plus 20 GiB free.
82
+
83
+ Redeploying never resizes the pool: an explicit `--disk-pool` that differs from
84
+ the existing size is refused, and omitting it keeps the pool as it is. To change
85
+ the size, back up the own-disk boxes, delete them, remove the pool explicitly
86
+ with Incus and redeploy.
87
+
88
+ ### Reservation accounting
89
+
90
+ A thin pool can promise its volumes more space than it holds, and when it fills
91
+ up every volume in it fails at once. Raft never lets that happen. A thin volume
92
+ can never hold more data than its own size, so as long as the sizes of all thin
93
+ volumes in the pool add up to no more than the pool's data space, the pool
94
+ cannot fill, however much the boxes write.
95
+
96
+ Under the host lock, before Incus adds a thin volume to `raft-disks`, Raft adds
97
+ up the size of every thin volume already there, as `lvs` reports it, and refuses
98
+ the request if the new volumes would not fit. The sum covers everything in the
99
+ pool: own-disk boxes, their snapshots, the cached image and anything an
100
+ operator created. Each operation adds:
101
+
102
+ | Operation | Adds |
103
+ | --- | --- |
104
+ | `new --disk SIZE` | The larger of SIZE and the cached image volume, plus the image volume itself the first time a box on this host needs it |
105
+ | `new --from BOX/SNAPSHOT` of an own-disk snapshot | The larger of the snapshot's size and `--disk` |
106
+ | `new --from BOX/SNAPSHOT --disk SIZE` of a shared-pool snapshot | SIZE |
107
+ | `fork` of an own-disk box | The box's size |
108
+ | `snapshot` of an own-disk box | The box's size |
109
+ | `recover` of an own-disk backup | The box's size once for the box and once per snapshot in the backup |
110
+
111
+ `restore` adds nothing: it swaps in a thin snapshot of the snapshot and removes
112
+ the old volume. Destroying a box or deleting a snapshot frees its reservation.
113
+ Incus caches the image in a 10 GiB volume (the pool's `volume.size` when set),
114
+ which stays reserved while the image is in use; plan the pool size as 10 GiB
115
+ plus the own disks and snapshots you want at once. The pool's data space is a
116
+ little below SIZE, because LVM keeps part of the file for thin pool metadata.
117
+
118
+ LVM sizes that metadata at 64 bytes per data block, above the 48 bytes per
119
+ block the kernel documentation recommends for a fully mapped pool, and
120
+ reservations keep the number of mapped blocks below the pool's block count. If
121
+ metadata ever filled, every own-disk box would stop and the pool would need
122
+ offline repair, so `raft capacity` warns when it passes 80%.
123
+
124
+ ## Inspecting capacity with `raft capacity`
125
+
126
+ ```sh title="CONTROLLER: View the advisory capacity estimate"
127
+ raft capacity
128
+
129
+ # Output JSON for programmatic inspection
130
+ raft capacity --location lab --json
131
+ ```
132
+
133
+ ```text
134
+ Advisory estimate only. Raft enforces no box count or size budget; create the boxes your task needs.
135
+ lab: 8 CPUs, 31.3 GiB RAM (27.9 GiB available)
136
+ Boxes: 3 saved, 2 active
137
+ Allocated to active boxes: 3 CPUs, 6.0 GiB; host reserve: 1 CPU, 3.1 GiB; headroom: 4 CPUs, 22.2 GiB
138
+ Disk free: 41.5 of 60.0 GiB shared pool, 48.2 GiB host
139
+ Own box disks: 19.9 of 39.9 GiB unreserved
140
+ CPU RAM GiB Fit on empty host Fit now
141
+ 1 2 7 4
142
+ 2 4 3 2
143
+ 4 8 1 1
144
+ Counts assume every box uses its full CPU and RAM at once. Boxes share host CPUs, so more can run when they are not all busy. Any whole CPU count and GiB size works.
145
+ ```
146
+
147
+ The table lists example shapes that double from the 1 CPU / 2 GiB default at
148
+ 2 GiB per CPU, up to the largest that fits the host. For any other size, divide
149
+ `headroom` by the box's CPU and RAM.
150
+
151
+ ### JSON fields
152
+
153
+ | Field | Meaning |
154
+ | --- | --- |
155
+ | `notice` | The advisory sentence above |
156
+ | `host` | CPUs, total and available RAM, free host disk, `sharedPool` space, and `boxDiskPool` |
157
+ | `host.boxDiskPool` | `null` when own box disks are not enabled. Otherwise `totalBytes` (thin pool data space), `reservedBytes` (sizes of all its thin volumes), `usedBytes` (data written) and `metadataUsedPercent`; the last two are `null` while no own-disk volume is active. New own disks fit in `totalBytes - reservedBytes` |
158
+ | `boxes`, `activeBoxes` | Saved boxes, and those not `Stopped` |
159
+ | `allocated` | CPU and RAM limits of active boxes |
160
+ | `reserve` | Host share left out of the estimates |
161
+ | `headroom` | CPU and RAM left for more active boxes after the reserve |
162
+ | `estimates` | Example shapes with `fitEmptyHost` and `fitNow` counts |
163
+ | `warnings` | Low RAM or disk, overcommitted boxes, or thin pool metadata above 80% |
164
+
165
+ ### Calculations
166
+
167
+ | Value | Calculation |
168
+ | --- | --- |
169
+ | CPU reserve | 1 CPU for the host OS |
170
+ | Memory reserve | The larger of 2 GiB or 10% of host RAM |
171
+ | Fit on empty host | `min(floor((CPUs - 1) / box CPUs), floor((RAM - reserve) / box RAM))` |
172
+ | Fit now | The same with `headroom`, which also subtracts active allocations and keeps `MemAvailable - reserve` |
173
+
174
+ All non-`Stopped` states, such as `Running` or `Frozen`, count as active.
175
+ Stopped boxes use no CPU or RAM and keep their disk. Reserves are planning
176
+ margins, not cgroup locks.
177
+
178
+ ### Illustrative hosts
179
+
180
+ Fit on empty host for 1 CPU / 2 GiB boxes, using exact usable RAM values:
181
+
182
+ | Host CPUs | Host RAM | Fit on empty host |
183
+ | ---: | ---: | ---: |
184
+ | 2 | 8 GiB | 1 |
185
+ | 4 | 8 GiB | 3 |
186
+ | 4 | 16 GiB | 3 |
187
+ | 8 | 32 GiB | 7 |
188
+
189
+ Operating systems report less usable RAM than the hardware size, so near
190
+ boundaries the estimate can be one lower. Available RAM and host processes can
191
+ lower it further. These are point-in-time estimates, not reservations.
192
+
193
+ ## Disk warnings
194
+
195
+ `raft capacity` warns when free host disk or the shared pool falls below 5 GiB,
196
+ and when `raft-disks` thin pool metadata passes 80%. Warnings do not block
197
+ anything. `raft doctor` reports the shared pool and, when own box disks are
198
+ enabled, `raft-disks`; a host without `raft-disks` is healthy.
@@ -14,6 +14,12 @@ Syntax:
14
14
  and `raft --version` prints the installed version without loading configuration.
15
15
  Output is command-specific; see [Scripting and CI](../guides/scripting-ci.mdx).
16
16
 
17
+ At most once a day, after a command finishes, Raft checks GitHub for a newer
18
+ release and prints one line on stderr, such as
19
+ `raft 0.6.0 is available (you have 0.5.0). Run: raft self-update`. It never
20
+ installs anything or changes stdout, and network failures stay silent. See
21
+ [`self-update`](#self-update) to turn it off.
22
+
17
23
  Workspaces are referenced by location-qualified handles formatted as `<location>:<instance-name>` (for example `lab:rf-a1b2c3d4e5f60718`).
18
24
 
19
25
  ## Command index
@@ -28,7 +34,9 @@ Workspaces are referenced by location-qualified handles formatted as `<location>
28
34
  | Port forwarding and desktop | [`forward`](#forward), [`desktop`](#desktop) |
29
35
  | Backup and recovery | [`backup`](#backup), [`recover`](#recover) |
30
36
  | Locations | [`host add`](#host-add), [`locations`](#locations), [`note`](#note) |
31
- | Host inspection and maintenance | [`limits`](#limits), [`doctor`](#doctor), [`list`](#list), [`gc`](#gc), [`prune`](#prune) |
37
+ | Defaults and policies | [`config`](#config), [`policy`](#policy) |
38
+ | Host inspection and maintenance | [`capacity`](#capacity), [`doctor`](#doctor), [`list`](#list), [`gc`](#gc), [`prune`](#prune) |
39
+ | CLI maintenance | [`self-update`](#self-update) |
32
40
 
33
41
  ## Workspace lifecycle commands
34
42
 
@@ -36,18 +44,28 @@ Workspaces are referenced by location-qualified handles formatted as `<location>
36
44
 
37
45
  Create and launch a new workspace container.
38
46
 
39
- `raft new [--location LOCATION] [--ttl TTL] [--cpu {1,2}] [--memory {1GiB,2GiB,4GiB,8GiB}] [--disposable] [--from LOCATION:BOX/SNAPSHOT]`
47
+ `raft new [--location LOCATION] [--ttl TTL] [--cpu N] [--memory SIZE] [--disk SIZE] [--disposable] [--from LOCATION:BOX/SNAPSHOT] [--purpose TEXT [--owner TEXT] [--next TEXT]]`
48
+
49
+ - `--location LOCATION`: Target host location in `~/.config/raft/incus.json`. Built-in default: first configured location.
50
+ - `--ttl TTL`: Lifetime in seconds before automatic stop (`60` to `2592000`). Built-in default: `600`.
51
+ - `--cpu N`: CPU limit, any whole number from `1` up to the host's CPU count. Built-in default: `1`, or the snapshot's with `--from`.
52
+ - `--memory SIZE`: Memory limit in whole GiB, for example `16GiB`, up to the host's RAM. Built-in default: `2GiB`, or the snapshot's with `--from`.
53
+ - `--disk SIZE`: Give the box its own disk of SIZE in whole GiB, for example `20GiB`, instead of the shared pool. Needs a host deployed with `--disk-pool`. The box, and Docker inside it, cannot write past SIZE, and the pool reserves SIZE when the box is created. `--disk shared` asks for the shared pool, overriding a configured `disk`. Built-in default: the shared pool, or with `--from` the snapshot's disk. See [Box sizing and own disks](./capacity.mdx#own-box-disks).
40
54
 
41
- - `--location LOCATION`: Target host location in `~/.config/raft/incus.json`. Defaults to first configured location.
42
- - `--ttl TTL`: Lifetime in seconds before automatic stop (`60` to `2592000`). Default: `600`.
43
- - `--cpu {1,2}`: CPU core count managed by Incus. Default: `1`.
44
- - `--memory {1GiB,2GiB,4GiB,8GiB}`: Memory limit. Default: `2GiB`.
55
+ An omitted option takes the controller default set with [`config`](#config),
56
+ then the built-in default. With `--from`, configured `cpu`, `memory` and `disk`
57
+ defaults do not apply: the box keeps the snapshot's size unless a flag overrides
58
+ it, and `--disk shared` is refused.
45
59
 
46
60
  - `--disposable`: Delete the box and snapshots on stop or expiry. Off by default.
47
- - `--from LOCATION:BOX/SNAPSHOT`: Copy a named snapshot on its source host instead of the configured image. Normal creation sizing applies; the source can be running. Cannot target a different location.
61
+ - `--from LOCATION:BOX/SNAPSHOT`: Copy a named snapshot on its source host instead of the configured image. The box keeps the snapshot's CPU, RAM and disk unless `--cpu`, `--memory` or `--disk` overrides them; the source can be running. Cannot target a different location.
62
+ - `--purpose TEXT`, `--owner TEXT`, `--next TEXT`: Record a [box note](#note). `--owner` and `--next` need `--purpose`. Without them the box starts with no note, including boxes created `--from` a noted snapshot. A host [policy](#policy) can require fields.
48
63
 
49
64
  Returns the qualified workspace handle. If the location has a note, `new` prints
50
- it to stderr; stdout still contains only the handle.
65
+ it to stderr; stdout still contains only the handle. Raft limits neither the
66
+ number of boxes nor their size; requests larger than the whole host, own disks
67
+ on a host without `raft-disks`, or own disks larger than the unreserved
68
+ `raft-disks` space, fail with the reason before anything is created.
51
69
 
52
70
  ### `stop`
53
71
 
@@ -61,12 +79,13 @@ Terminates guest processes and stops the container. Persistent boxes retain file
61
79
 
62
80
  Start a stopped workspace with a renewed lifetime deadline.
63
81
 
64
- `raft resume <box> --ttl TTL [--cpu {1,2}] [--memory {1GiB,2GiB,4GiB,8GiB}]`
82
+ `raft resume <box> --ttl TTL [--cpu N] [--memory SIZE]`
65
83
 
66
84
  - `--ttl TTL`: Lifetime in seconds from resumption (`60` to `2592000`). Required.
67
85
 
68
86
  CPU/RAM overrides on resume apply before startup. Omitted values stay unchanged.
69
- A running box is rejected before changing its limits.
87
+ A running box is rejected before changing its limits. Resume never changes the
88
+ box's disk.
70
89
 
71
90
  ### `extend`
72
91
 
@@ -82,7 +101,7 @@ Permanently delete a workspace container and its snapshots.
82
101
 
83
102
  `raft destroy <box>`
84
103
 
85
- Stops the container if running, removes all associated Btrfs snapshots, and releases host storage.
104
+ Stops the container if running, removes all associated snapshots, and releases its storage, including an own disk.
86
105
 
87
106
  ## Execution and inspection commands
88
107
 
@@ -108,13 +127,13 @@ Open an interactive root terminal session inside the workspace over host SSH and
108
127
 
109
128
  ### `info`
110
129
 
111
- Display container metadata from Incus in JSON format, including status, IP addresses, resource limits, and timestamps.
130
+ Display container metadata from Incus in JSON format, including status, IP addresses, resource limits, and timestamps. `expanded_devices.root` shows the disk: `pool: raft-data` for the shared pool, or `pool: raft-disks` with the own disk's `size`.
112
131
 
113
132
  `raft info <box>`
114
133
 
115
134
  ### `usage`
116
135
 
117
- Display active resource consumption for a running workspace: cgroup memory, CPU microseconds, CPU affinity, and shared-pool disk space.
136
+ Display active resource consumption for a running workspace: cgroup memory, CPU microseconds, CPU affinity, and filesystem space. `filesystemScope` says whether the space is the shared pool or the box's own disk.
118
137
 
119
138
  `raft usage <box>`
120
139
 
@@ -174,6 +193,8 @@ Create a named point-in-time snapshot of a stopped workspace. Names use alphanum
174
193
 
175
194
  `raft snapshot <box> <name>`
176
195
 
196
+ A snapshot of a box with its own disk is a thin snapshot that reserves the box's disk size in `raft-disks`; it fails before anything is created when the pool lacks unreserved space.
197
+
177
198
  ### `snapshots`
178
199
 
179
200
  List existing snapshots and creation timestamps for a workspace.
@@ -196,11 +217,13 @@ Roll back a stopped workspace filesystem to a named snapshot using `--diskonly`,
196
217
 
197
218
  Clone a stopped workspace into an independent container on the same host and start it.
198
219
 
199
- `raft fork <box> --ttl TTL [--cpu {1,2}] [--memory {1GiB,2GiB,4GiB,8GiB}] [--disposable]`
220
+ `raft fork <box> --ttl TTL [--cpu N] [--memory SIZE] [--disposable] [--purpose TEXT [--owner TEXT] [--next TEXT]]`
200
221
 
201
222
  - `--ttl TTL`: Lifetime in seconds for the clone. Required.
223
+ - `--purpose TEXT`, `--owner TEXT`, `--next TEXT`: Replace the clone's whole note. Without them the clone keeps the source note.
202
224
 
203
- Omitted CPU/RAM values inherit the source limits. The clone is persistent unless `--disposable` is supplied, regardless of the source policy.
225
+ Omitted CPU/RAM values inherit the source limits; controller defaults never apply to forks. The clone keeps the source's disk: the shared pool, or an own disk of the same size as a thin snapshot, which reserves that size in `raft-disks`. To copy a box onto a disk of another size, snapshot it and use `new --from BOX/SNAPSHOT --disk SIZE`. The clone is persistent unless `--disposable` is supplied, regardless of the source policy.
226
+ The note the clone ends up with must satisfy the host [policy](#policy).
204
227
 
205
228
  Returns the qualified handle of the clone.
206
229
 
@@ -239,12 +262,17 @@ Export a stopped workspace and its snapshots to a compressed archive on the cont
239
262
 
240
263
  Import a portable backup archive onto a host as a fresh stopped workspace.
241
264
 
242
- `raft recover <source> --location LOCATION`
265
+ `raft recover <source> --location LOCATION [--purpose TEXT [--owner TEXT] [--next TEXT]]`
243
266
 
244
267
  - `<source>`: Path to archive file on controller.
245
268
  - `--location LOCATION`: Target host location. Required.
269
+ - `--purpose TEXT`, `--owner TEXT`, `--next TEXT`: Replace the recovered box's whole note. Without them it keeps the archived note.
270
+
271
+ Under a host [policy](#policy), a given note is checked before the upload. An
272
+ archived note is checked after import; if it fails, the imported box is deleted
273
+ in the same locked step.
246
274
 
247
- Returns the qualified handle of the imported stopped workspace.
275
+ Returns the qualified handle of the imported stopped workspace. A backup of a box with its own disk is recovered onto an own disk of the same size in `raft-disks`, which must exist on the destination and have room to reserve that size for the box and each of its snapshots; any other backup lands in the shared pool.
248
276
 
249
277
  ## Location commands
250
278
 
@@ -252,19 +280,20 @@ Returns the qualified handle of the imported stopped workspace.
252
280
 
253
281
  Check, provision and register a new workspace host, then prove it works.
254
282
 
255
- `raft host add LOCATION --ssh TARGET [--image-release TAG | --image-archive PATH | --build] [--dry-run] [--yes]`
283
+ `raft host add LOCATION --ssh TARGET [--image-release TAG | --image-archive PATH | --build] [--disk-pool SIZE] [--dry-run] [--yes]`
256
284
 
257
285
  - `LOCATION`: New location name. Existing locations are rejected.
258
286
  - `--ssh TARGET`: Key-based SSH target, such as `ubuntu@192.0.2.10` or an `~/.ssh/config` alias. Accept the host key interactively first.
259
287
  - `--image-release TAG`: Download `raft-dev-<arch>.tar.gz` from this release on the host and verify it against the release `SHA256SUMS`. Default: the CLI's own version.
260
288
  - `--image-archive PATH`: Upload and import a local image archive instead.
261
289
  - `--build`: Build the image on the host from Debian 13 instead.
290
+ - `--disk-pool SIZE`: Enable [own box disks](./capacity.mdx#own-box-disks) with a `raft-disks` thin pool of SIZE, in whole GiB. Default: not enabled. The pool's file takes its full size on the host disk when created. To enable them on a host that is already deployed, run `python3 deploy/deploy.py --location LOCATION --disk-pool SIZE`. An existing pool keeps its size; a different explicit value is refused.
262
291
  - `--dry-run`: Probe the host and print the plan without changing anything.
263
292
  - `--yes`: Apply the plan without the interactive prompt. Without a terminal, `--yes` is required.
264
293
 
265
294
  Before any change, a read-only probe checks SSH, passwordless sudo, Ubuntu 22.04 or
266
295
  24.04, ARM64 or AMD64, python3, at least 2 CPUs, 8 GiB RAM and, for a fresh host,
267
- 80 GiB free disk. Unsupported targets, including macOS, fail with the reasons and
296
+ 80 GiB of free disk, plus the `--disk-pool` size when given. Unsupported targets, including macOS, fail with the reasons and
268
297
  no changes. The command then runs the deployer, imports the image only if its
269
298
  checksum and architecture match, adds the location with the pinned fingerprint,
270
299
  and runs `doctor` plus a disposable create/exec/destroy smoke test. If the final
@@ -291,31 +320,101 @@ Prints one JSON object per location (JSONL) in configuration order, with
291
320
 
292
321
  ### `note`
293
322
 
294
- Set or clear operator guidance for a location in `~/.config/raft/incus.json`.
323
+ Set or clear the note of a location or a box.
295
324
 
296
325
  `raft note LOCATION TEXT`
297
326
  `raft note LOCATION --clear`
327
+ `raft note BOX --purpose TEXT [--owner TEXT] [--next TEXT]`
328
+ `raft note BOX --clear`
329
+
330
+ Location notes are operator guidance stored in `~/.config/raft/incus.json`. Use
331
+ them to record which host is preferred or what a host should not run. Raft shows
332
+ them in `locations`, `capacity` and `new`.
333
+
334
+ Box notes tell other agents and people why a box exists. They follow one
335
+ template, stored on the box as JSON in the Incus key `user.raft.note`:
336
+
337
+ | Field | Meaning | Example |
338
+ | --- | --- | --- |
339
+ | `purpose` (required) | What the box is for | `Integration tests for PR 42` |
340
+ | `owner` | Who uses it | `codex session in ~/work/app` |
341
+ | `next` | What happens to it next | `Destroy after the tests pass` |
342
+
343
+ Each call replaces the whole note, on running or stopped boxes. `raft list`
344
+ shows it as `note`. Forks and recovered backups keep the source box's note
345
+ unless `fork` or `recover` gets note flags; update it if the copy has a
346
+ different job.
347
+
348
+ Empty values are rejected; use `--clear` to remove a note. A host
349
+ [policy](#policy) can require fields, and then rejects notes and `--clear` that
350
+ would leave them out. Otherwise Raft does not interpret notes.
351
+
352
+ ### `config`
353
+
354
+ Show or change the controller defaults for `new`.
355
+
356
+ `raft config`
357
+ `raft config set KEY VALUE`
358
+ `raft config unset KEY`
359
+
360
+ - `KEY`: One of `location`, `ttl`, `cpu`, `memory`, `disk`.
361
+ - `VALUE`: Accepted exactly when the same text is accepted by the matching `new` flag. A `location` must be configured.
362
+
363
+ `cpu`, `memory` and `disk` size only boxes created from the image. `new --from`
364
+ and `fork` keep the source's CPU, RAM and disk; only explicit flags change them.
365
+ A `disk` default on a location without `raft-disks`, or larger than its
366
+ unreserved space, fails `new` with the same error as `--disk`, plus a sentence
367
+ naming `defaults.disk` as the source. `--disk shared` overrides it for one box.
368
+
369
+ Defaults live in `~/.config/raft/config.json` as `{"defaults": {"KEY": VALUE}}`.
370
+ `set` and `unset` print the old and new value, write atomically under a
371
+ controller lock and keep mode `0600`. An unknown key, bad value or malformed
372
+ file makes `new` fail with the file path and key; `set` or `unset` that key to
373
+ repair it.
374
+
375
+ `raft config` prints one JSON object: `file`, `defaults` (each key's `value` and
376
+ `source`, `config` or `built-in`) and `policy` (each location's `require` list
377
+ and `changed` record). It contacts every configured host to read policies.
378
+ See [Defaults and note policies](../guides/config-policy.mdx).
298
379
 
299
- Notes are advisory. Use them to record which host is preferred or what a host
300
- should not run. Raft shows notes in `locations`, `limits` and `new` but does not
301
- enforce them. Empty notes are rejected; use `--clear` to remove one.
380
+ ### `policy`
381
+
382
+ Set or clear the box note fields a location's host requires.
383
+
384
+ `raft policy LOCATION --require FIELDS`
385
+ `raft policy LOCATION --clear`
386
+
387
+ - `--require FIELDS`: Comma-separated fields from `purpose`, `owner`, `next`. Must include `purpose`.
388
+ - `--clear`: Stop requiring fields.
389
+
390
+ The policy is stored on the host in the Incus project key `user.raft.require`,
391
+ so it applies to every controller. `new`, `fork`, `recover` and `note` check it
392
+ on the controller for an actionable error, then again in the host transaction
393
+ that writes the note. The command prints the change and the host records the
394
+ time, SSH account and controller in `user.raft.require.changed` and the host
395
+ journal (`journalctl -t raft`).
396
+
397
+ This is a guardrail for agents, not a security boundary: SSH and sudo on the
398
+ host can change the policy or bypass it, and older controllers do not check it.
302
399
 
303
400
  ## Host inspection and maintenance commands
304
401
 
305
- ### `limits`
402
+ ### `capacity`
306
403
 
307
- Inspect host resource capacity, active allocations, and recommended workspace limits.
404
+ Estimate how many more boxes a host can run well. The report is advice: Raft
405
+ enforces no box count or size budget, so create the boxes your task needs.
308
406
 
309
- `raft limits [--location LOCATION] [--json]`
407
+ `raft capacity [--location LOCATION] [--json]`
310
408
 
311
409
  - `--location LOCATION`: Filter report to a specific host.
312
410
  - `--json`: Output report as JSON.
313
411
 
314
- Each report includes the location's `note`, if any.
412
+ Each report leads with that advisory notice and includes the location's `note`,
413
+ if any. See [Box sizing and capacity](./capacity.mdx) for the fields.
315
414
 
316
415
  ### `doctor`
317
416
 
318
- Inspect host connectivity, Incus version, expiry-worker compatibility, active systemd units (`incus`, `raft-expire.timer`, `raft-network.service`), root filesystem usage, storage pool status, and saved box count across configured hosts. An incompatible worker returns a nonzero exit status and names the failing location without running expiry or modifying boxes.
417
+ Inspect host connectivity, Incus version, expiry-worker compatibility, active systemd units (`incus`, `raft-expire.timer`, `raft-network.service`), root filesystem usage, status of the shared `raft-data` pool and, when own box disks are enabled, the `raft-disks` pool, and saved box count across configured hosts. Without `raft-disks` it prints `own box disks: not enabled (redeploy with --disk-pool SIZE)` and does not fail. An incompatible worker returns a nonzero exit status and names the failing location without running expiry or modifying boxes.
319
418
 
320
419
  `raft doctor`
321
420
 
@@ -325,7 +424,9 @@ List existing workspace containers across configured hosts.
325
424
 
326
425
  `raft list [--location LOCATION]`
327
426
 
328
- Prints one JSON object per box (JSONL), with `box`, `state`, and `stopAt` fields.
427
+ Prints one JSON object per box (JSONL), with `box`, `state`, `stopAt`, `disk` and `note` fields.
428
+ `disk` is `"shared"` for the shared pool or the own disk's size, such as `"20GiB"`.
429
+ `note` is the box note object, or `null` when the box has none.
329
430
  An empty inventory prints no records. See [Scripting and CI](../guides/scripting-ci.mdx)
330
431
  for output formats, exit codes, readiness, and failure handling.
331
432
 
@@ -344,3 +445,39 @@ Preview deletion of persistent stopped boxes older than the selected number of d
344
445
  Days must be at least one. Without `--yes`, this only prints JSONL candidates. With `--yes`, it deletes matching boxes and their snapshots under the host lock. Running boxes and boxes without a recorded stop time are excluded. Stop timestamps come from Raft stop/expiry, not box creation. No automatic retention policy is enabled.
345
446
 
346
447
  Disposable creation/fork and prune require the current expiry worker installed through the deployer. A mismatch fails with deployment guidance; the CLI does not install or adapt an older worker.
448
+
449
+ ## CLI maintenance commands
450
+
451
+ ### `self-update`
452
+
453
+ Upgrade the installed CLI to the latest GitHub release, or to a chosen one.
454
+
455
+ `raft self-update [--check | --version X.Y.Z]`
456
+
457
+ - `--check`: Report whether a newer release exists without installing it. Exits `0` when the CLI is current and `100` when an update is available.
458
+ - `--version X.Y.Z`: Install this release instead of the latest, for example to roll back. Without it, `self-update` never downgrades.
459
+
460
+ The latest release on GitHub decides what is current. `self-update` installs
461
+ through the channel that installed the running copy and never switches channel:
462
+
463
+ | Installed with | Detected by | Upgrade | Integrity |
464
+ | --- | --- | --- | --- |
465
+ | `npm install -g @microck/raft` | `PREFIX/lib/node_modules/@microck/raft`, linked from `PREFIX/bin/raft` | `npm install --global --prefix PREFIX @microck/raft@X.Y.Z` | npm checks the registry integrity hash; Raft publishes with npm provenance |
466
+ | `uv tool install` of a release wheel | A uv tool environment named `raft` | Downloads the wheel and `SHA256SUMS` from the release, then `uv tool install --force` | Refuses a wheel whose SHA-256 differs from the release `SHA256SUMS` |
467
+
468
+ Source checkouts, editable installs, pip and other layouts are refused with
469
+ instructions. For a uv tool, `uv tool dir` must still name the original tool
470
+ directory; set `UV_TOOL_DIR` if it moved.
471
+
472
+ `self-update` prints `raft OLD -> NEW` and the release notes link. A current CLI
473
+ is a no-op. Hosts are not changed: run [`doctor`](#doctor) next. If a host's
474
+ worker differs from the new CLI, redeploy that host from the same release; see
475
+ [Upgrades and image changes](../guides/upgrades.mdx). After an update,
476
+ [`host add`](#host-add) downloads the new release's image by default. Existing
477
+ image pins stay unchanged.
478
+
479
+ Set `RAFT_NO_UPDATE_CHECK=1` to turn off the daily release notice, for example in
480
+ pinned or scripted runs. The notice caches its last result in
481
+ `$XDG_CACHE_HOME/raft/update.json` (default `~/.cache/raft/update.json`). It
482
+ waits at most 2 seconds for GitHub and is skipped for `self-update` and
483
+ `--version`.
@@ -40,7 +40,7 @@ The configuration file maps location names to host definition objects:
40
40
  ### Location keys
41
41
 
42
42
  - **Naming format**: Matches `^[a-zA-Z0-9][a-zA-Z0-9_-]*$`. Starts with an alphanumeric character and contains ASCII letters, digits, underscores, or hyphens.
43
- - **Default location**: The first location listed is the default for `raft new`. `raft list`, `raft limits`, `raft gc`, and `raft prune` accept `--location` filters. Without a filter they check all configured hosts; `raft doctor` always checks all configured hosts.
43
+ - **Default location**: The first location listed is the default for `raft new`. `raft list`, `raft capacity`, `raft gc`, and `raft prune` accept `--location` filters. Without a filter they check all configured hosts; `raft doctor` always checks all configured hosts.
44
44
 
45
45
  ### Host entry fields
46
46
 
@@ -60,7 +60,7 @@ raft note spare --clear
60
60
  ```
61
61
 
62
62
  `raft note` rewrites the file with two-space indentation, keeps location order and
63
- mode `0600`. `raft locations`, `raft limits` and `raft new` show the note.
63
+ mode `0600`. `raft locations`, `raft capacity` and `raft new` show the note.
64
64
 
65
65
  <Callout type="warn" title="Replace placeholder image fingerprint">
66
66
  The `image` field requires the full 64-character hash for `raft new`; aliases such as `raft-dev` are rejected. Replace the example hash with the actual imported image fingerprint for that host.
@@ -68,6 +68,40 @@ The `image` field requires the full 64-character hash for `raft new`; aliases su
68
68
  ARM64 and AMD64 images produce different fingerprints; do not reuse an ARM64 hash on an AMD64 host.
69
69
  </Callout>
70
70
 
71
+ ## Defaults for new
72
+
73
+ `~/.config/raft/config.json` is optional. It sets defaults for `raft new` on this
74
+ controller and is separate from the location inventory:
75
+
76
+ ```json title="Example ~/.config/raft/config.json"
77
+ {
78
+ "defaults": {
79
+ "location": "lab",
80
+ "ttl": 3600,
81
+ "cpu": 2,
82
+ "memory": "4GiB",
83
+ "disk": "20GiB"
84
+ }
85
+ }
86
+ ```
87
+
88
+ | Key | Accepted values | Built-in default |
89
+ | :--- | :--- | :--- |
90
+ | `location` | A configured location name | First configured location |
91
+ | `ttl` | `60` to `2592000` seconds | `600` |
92
+ | `cpu` | A positive whole number | `1` |
93
+ | `memory` | Whole GiB, e.g. `16GiB` | `2GiB` |
94
+ | `disk` | Whole GiB for an own box disk, or `shared` | `shared` |
95
+
96
+ Explicit flags win over these values. `cpu`, `memory` and `disk` size only boxes
97
+ created from the image: `new --from` and `fork` keep the source's size unless a
98
+ flag overrides it. A `disk` default needs the `raft-disks` pool on the target
99
+ host, like `--disk`. Unknown keys and invalid values make
100
+ `raft new` fail with the file path and key. Edit the file with
101
+ `raft config set KEY VALUE` and `raft config unset KEY`, and inspect it with
102
+ `raft config`. Required box note fields are a host policy, not part of this
103
+ file; see [Defaults and note policies](../guides/config-policy.mdx).
104
+
71
105
  ## SSH configuration and connection behavior
72
106
 
73
107
  Primary remote commands (`raft ssh`, `upload`, `download`, and control commands) run OpenSSH with `BatchMode=yes` and `-o ConnectTimeout=10` to fail fast on unreachable hosts. The timeout applies to initial connection establishment, not session or transfer duration.
@@ -5,6 +5,6 @@
5
5
  "pages": [
6
6
  "cli",
7
7
  "configuration",
8
- "capacity-limits"
8
+ "capacity"
9
9
  ]
10
10
  }
@@ -0,0 +1,15 @@
1
+ ---
2
+ title: Raft 0.4.0
3
+ ---
4
+
5
+ # Raft 0.4.0
6
+
7
+ - Boxes accept a note that says why they exist, so agents and people can tell which boxes are in use. The note follows one template: `purpose` (required), `owner` and `next`.
8
+ - `raft new --purpose TEXT [--owner TEXT] [--next TEXT]` notes a box when it is created. `raft note BOX --purpose ...` replaces a box's note and `raft note BOX --clear` removes it. `raft note LOCATION ...` still edits location notes.
9
+ - `raft list` adds a `note` field to each box: the note object, or `null`.
10
+ - Forks and recovered backups keep the source box's note. `new --from` starts without one.
11
+ - The `raft-cli` agent skill asks agents to note every box they create and to read notes before resuming, stopping, forking or destroying a box.
12
+
13
+ Install on Linux with Python 3.11.8+ using `npm install -g @microck/raft@0.4.0`, or install the attached Python wheel. Box notes live in each box's Incus configuration; host workers are unchanged and need no redeployment.
14
+
15
+ ARM64 and AMD64 development images accompany this release. Verify `SHA256SUMS` before importing an image for your host architecture.
@@ -0,0 +1,19 @@
1
+ ---
2
+ title: Raft 0.5.0
3
+ ---
4
+
5
+ # Raft 0.5.0
6
+
7
+ - **No box cap.** A location no longer stops at four saved boxes. Agents create the boxes their task needs; creation only fails for real reasons such as a full pool.
8
+ - **Any size.** `--cpu N` and `--memory NGiB` accept any positive size up to the host's CPUs and RAM. A box from the image gets 1 CPU, 2 GiB and the shared disk; `new --from` and `fork` keep the source's CPU, RAM and disk, and each flag overrides only its own value.
9
+ - **Own box disks.** `raft new --disk SIZE` gives a box its own ext4 disk with a hard cap that also holds for Docker inside the box. Own disks are opt-in per host: redeploy with `--disk-pool SIZE` to create the thin LVM pool `raft-disks`. New boxes, forks, template copies and snapshots are copy-on-write, and a reservation check refuses any request that would overcommit the pool. `raft list` shows each box's `disk`.
10
+ - **`raft capacity` replaces `raft limits`.** It is advisory only: it reports host CPUs, RAM, allocations, both pools and how many more boxes of example sizes fit, and never limits creation. JSON fields changed (`boxes`, `estimates`, `sharedPool`, `boxDiskPool`, `headroom`, `notice`).
11
+ - **`raft self-update`.** Updates the CLI through the channel that installed it: npm, or a uv tool from the release wheel verified against `SHA256SUMS`. `--check` reports without installing (exit 100 when an update exists) and `--version X.Y.Z` installs a specific release. A notice on stderr at most once a day says when a newer release exists; `RAFT_NO_UPDATE_CHECK=1` turns it off. It never installs on its own and never changes hosts.
12
+ - **Controller defaults.** `raft config`, `raft config set KEY VALUE` and `raft config unset KEY` manage `~/.config/raft/config.json` with defaults for `location`, `ttl`, `cpu`, `memory` and `disk`. A flag beats the config, which beats the built-in default. `--disk shared` overrides a configured disk default for one box.
13
+ - **Note policies.** `raft policy LOCATION --require purpose,owner` makes a host require those box note fields. `new`, `fork`, `recover` and `note` refuse a box without them, with an error naming the missing flag, and the host checks again under its lock. Policy changes are recorded on the host and in its journal. This is a guardrail for agents, not a security boundary.
14
+
15
+ Install on Linux with Python 3.11.8+ using `npm install -g @microck/raft@0.5.0`, or install the attached Python wheel; existing installs can run `raft self-update`.
16
+
17
+ Redeploy each host with this release's `deploy/deploy.py`. It installs `lvm2` and `python3-yaml`, which `raft recover` now needs to read backups, and changes nothing else unless you add `--disk-pool SIZE` to enable own box disks. That needs SIZE plus 20 GiB free on the host; existing storage is never resized.
18
+
19
+ ARM64 and AMD64 development images accompany this release. Verify `SHA256SUMS` before importing an image for your host architecture.
@@ -17,11 +17,11 @@ Each workspace host must be a dedicated physical machine or virtual instance:
17
17
  | **Operating system** | Ubuntu 22.04 LTS or 24.04 LTS | The deployer installs Incus 6.0 LTS and nftables bridge isolation rules. |
18
18
  | **CPU** | At least 2 CPU cores | Capacity calculations reserve 1 CPU core for the host OS and services (an advisory estimate, not an enforced host reservation). |
19
19
  | **RAM** | At least 8 GiB | Sizing calculations reserve the larger of 2 GiB or 10% of host RAM (an advisory estimate, not an enforced host reservation). |
20
- | **Free disk** | At least 80 GiB on host disk | The deployer provisions a 60 GiB dedicated Btrfs pool (`raft-data`). |
20
+ | **Free disk** | At least 80 GiB on host disk | For the 60 GiB shared Btrfs pool (`raft-data`), images and the host. [Own box disks](./reference/capacity.mdx#own-box-disks) are optional: `--disk-pool SIZE` adds a `raft-disks` pool of SIZE, so a fresh host then needs 80 GiB plus SIZE, and an existing host SIZE plus 20 GiB. |
21
21
 
22
22
  `raft host add` checks these requirements read-only before it changes a host.
23
23
 
24
- Host CPU and RAM reserves are advisory estimates for `raft limits`, not enforced cgroup locks.
24
+ Host CPU and RAM reserves are advisory estimates for `raft capacity`, not enforced cgroup locks. Raft limits neither how many boxes a host runs nor their size.
25
25
 
26
26
  <Callout type="warn" title="Dedicated host requirement">
27
27
  Do not deploy Raft on hosts with existing Incus storage pools or shared virtualization workloads. Deployment configures dedicated nftables rules, storage pools, and system services.