@microck/raft 0.5.0 → 0.6.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.
- package/README.md +13 -10
- package/deploy/raft-expire.py +109 -31
- package/deploy/verify-controls.py +1 -1
- package/deploy/verify-features.py +294 -17
- package/deploy/verify-limits.py +2 -1
- package/deploy/verify-live.py +19 -3
- package/docs/contract.md +12 -11
- package/docs/first-box.mdx +1 -1
- package/docs/guides/backup-recovery.mdx +1 -1
- package/docs/guides/config-policy.mdx +109 -35
- package/docs/guides/long-running-services.mdx +8 -2
- package/docs/guides/scripting-ci.mdx +8 -5
- package/docs/guides/snapshots-forks.mdx +1 -1
- package/docs/guides/troubleshooting.mdx +19 -2
- package/docs/guides/upgrades.mdx +4 -2
- package/docs/index.mdx +1 -1
- package/docs/operations.mdx +10 -10
- package/docs/reference/cli.md +51 -25
- package/docs/reference/configuration.mdx +10 -6
- package/docs/releases/0.6.0.md +16 -0
- package/docs/verification.mdx +1 -1
- package/package.json +1 -1
- package/raft.py +163 -31
- package/skills/raft-cli/SKILL.md +21 -6
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: Defaults and
|
|
3
|
-
description: Set controller defaults for new boxes
|
|
2
|
+
title: Defaults and host policies
|
|
3
|
+
description: Set controller defaults for new boxes, require box note fields on a host and delete boxes left stopped.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Defaults and
|
|
6
|
+
# Defaults and host policies
|
|
7
7
|
|
|
8
8
|
Raft has two kinds of settings:
|
|
9
9
|
|
|
10
10
|
| Setting | Stored in | Applies to | Change with |
|
|
11
11
|
| --- | --- | --- | --- |
|
|
12
12
|
| Defaults for `new` | `~/.config/raft/config.json` on the controller | That controller only | `raft config set` and `raft config unset` |
|
|
13
|
-
| Required box note fields | Incus project `raft` on each host | Every controller that uses the host | `raft policy` |
|
|
13
|
+
| Required box note fields | Incus project `raft` on each host | Every controller that uses the host | `raft policy --require` |
|
|
14
|
+
| Stopped-box retention | Incus project `raft` on each host | Every box on the host | `raft policy --delete-stopped-after` |
|
|
14
15
|
|
|
15
|
-
`raft config` prints both: the effective defaults with their source, and
|
|
16
|
-
location's
|
|
16
|
+
`raft config` prints both kinds: the effective defaults with their source, and
|
|
17
|
+
each location's policies.
|
|
17
18
|
|
|
18
19
|
```sh title="CONTROLLER: Inspect defaults and policies"
|
|
19
20
|
raft config
|
|
@@ -42,6 +43,10 @@ raft config
|
|
|
42
43
|
"disk": {
|
|
43
44
|
"value": "shared",
|
|
44
45
|
"source": "built-in"
|
|
46
|
+
},
|
|
47
|
+
"disposable": {
|
|
48
|
+
"value": true,
|
|
49
|
+
"source": "config"
|
|
45
50
|
}
|
|
46
51
|
},
|
|
47
52
|
"policy": {
|
|
@@ -50,7 +55,10 @@ raft config
|
|
|
50
55
|
"purpose",
|
|
51
56
|
"owner"
|
|
52
57
|
],
|
|
53
|
-
"
|
|
58
|
+
"requireChanged": "2026-10-21T09:12:44Z by ubuntu from me@laptop",
|
|
59
|
+
"deleteStoppedAfterHours": 48,
|
|
60
|
+
"deleteStoppedAfterSince": 1792573964,
|
|
61
|
+
"deleteStoppedAfterChanged": "2026-10-21T09:12:44Z by ubuntu from me@laptop"
|
|
54
62
|
}
|
|
55
63
|
}
|
|
56
64
|
}
|
|
@@ -61,33 +69,42 @@ names the location when a host is unreachable.
|
|
|
61
69
|
|
|
62
70
|
## Defaults for new
|
|
63
71
|
|
|
64
|
-
Set a default for any of `location`, `ttl`, `cpu`, `memory` and
|
|
72
|
+
Set a default for any of `location`, `ttl`, `cpu`, `memory`, `disk` and
|
|
73
|
+
`disposable`:
|
|
65
74
|
|
|
66
|
-
```sh title="CONTROLLER: Prefer bigger, longer boxes on paris"
|
|
75
|
+
```sh title="CONTROLLER: Prefer bigger, longer, temporary boxes on paris"
|
|
67
76
|
raft config set location paris
|
|
68
77
|
raft config set ttl 3600
|
|
69
78
|
raft config set cpu 4
|
|
70
79
|
raft config set memory 16GiB
|
|
71
80
|
raft config set disk 20GiB
|
|
81
|
+
raft config set disposable true
|
|
72
82
|
raft config unset cpu
|
|
73
83
|
```
|
|
74
84
|
|
|
75
85
|
Each command prints the old and new value. `raft new` takes an explicit flag
|
|
76
86
|
first, then the configured default, then the built-in default (first configured
|
|
77
|
-
location, 600 seconds, 1 CPU, 2 GiB, the shared pool). `resume`,
|
|
78
|
-
`extend` and `recover` take no defaults: their TTL and target are
|
|
79
|
-
choices, and omitted sizes on resume and fork keep the source limits.
|
|
87
|
+
location, 600 seconds, 1 CPU, 2 GiB, the shared pool, persistent). `resume`,
|
|
88
|
+
`fork`, `extend` and `recover` take no defaults: their TTL and target are
|
|
89
|
+
deliberate choices, and omitted sizes on resume and fork keep the source limits.
|
|
90
|
+
|
|
91
|
+
With `disposable true`, every `raft new` box is deleted, with its snapshots, when
|
|
92
|
+
it stops or expires. `raft new --persistent` keeps one box anyway, and
|
|
93
|
+
`--disposable` overrides `disposable false` the same way. The default applies to
|
|
94
|
+
`new --from` too: a snapshot has no lifecycle of its own. A fork stays
|
|
95
|
+
persistent unless it gets `--disposable`, whatever the default says, like every
|
|
96
|
+
other fork setting.
|
|
80
97
|
|
|
81
98
|
Size defaults (`cpu`, `memory`, `disk`) apply only where the built-in sizes do:
|
|
82
99
|
to boxes created from the image. A box from `new --from` keeps the snapshot's
|
|
83
100
|
location, CPU, RAM and disk, and a fork keeps its source's, whatever
|
|
84
101
|
`config.json` says. Only a flag changes them:
|
|
85
102
|
|
|
86
|
-
| Command | CPU and RAM | Disk |
|
|
87
|
-
| --- | --- | --- |
|
|
88
|
-
| `new` | Flag, then config, then 1 CPU / 2 GiB | Flag, then config, then the shared pool |
|
|
89
|
-
| `new --from` | Flag, then the snapshot's | `--disk SIZE`, then the snapshot's |
|
|
90
|
-
| `fork` | Flag, then the source's | The source's |
|
|
103
|
+
| Command | CPU and RAM | Disk | Disposable |
|
|
104
|
+
| --- | --- | --- | --- |
|
|
105
|
+
| `new` | Flag, then config, then 1 CPU / 2 GiB | Flag, then config, then the shared pool | Flag, then config, then persistent |
|
|
106
|
+
| `new --from` | Flag, then the snapshot's | `--disk SIZE`, then the snapshot's | Flag, then config, then persistent |
|
|
107
|
+
| `fork` | Flag, then the source's | The source's | `--disposable`, else persistent |
|
|
91
108
|
|
|
92
109
|
A `disk` default needs own box disks on the target host. On a location without
|
|
93
110
|
the `raft-disks` pool, or with too little unreserved space, `raft new` fails with
|
|
@@ -107,9 +124,9 @@ path and the key; Raft never ignores part of the file. Repair a bad key with
|
|
|
107
124
|
`raft config set KEY VALUE` or `raft config unset KEY`. Writes are atomic, keep
|
|
108
125
|
mode `0600` and remove the file when no default remains.
|
|
109
126
|
|
|
110
|
-
Scripts and CI jobs that need stable
|
|
111
|
-
`--cpu`, `--memory` and `--
|
|
112
|
-
controllers.
|
|
127
|
+
Scripts and CI jobs that need stable boxes should pass `--location`, `--ttl`,
|
|
128
|
+
`--cpu`, `--memory`, `--disk` and `--disposable` or `--persistent` explicitly,
|
|
129
|
+
because defaults differ between controllers.
|
|
113
130
|
|
|
114
131
|
## Required box note fields
|
|
115
132
|
|
|
@@ -118,7 +135,7 @@ fields. `purpose` is always part of a note, so a policy must include it:
|
|
|
118
135
|
|
|
119
136
|
```sh title="CONTROLLER: Require an owner on lab"
|
|
120
137
|
raft policy lab --require purpose,owner
|
|
121
|
-
raft policy lab --
|
|
138
|
+
raft policy lab --require none
|
|
122
139
|
```
|
|
123
140
|
|
|
124
141
|
With that policy, a command that would leave a box without an owner fails before
|
|
@@ -151,29 +168,86 @@ so a policy change cannot race a creation. That transaction checks the
|
|
|
151
168
|
requested size against the host and reserves own-disk space first; any refusal
|
|
152
169
|
comes before Incus creates the box.
|
|
153
170
|
|
|
171
|
+
## Delete boxes left stopped
|
|
172
|
+
|
|
173
|
+
A retention policy makes a host delete boxes that stay stopped too long, with
|
|
174
|
+
their snapshots. Use it when agents create boxes and forget them:
|
|
175
|
+
|
|
176
|
+
```sh title="CONTROLLER: Delete boxes stopped for 48 hours on lab"
|
|
177
|
+
raft policy lab --delete-stopped-after 48
|
|
178
|
+
raft policy lab --delete-stopped-after never
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The host expiry worker applies it every 15 seconds, and on `raft gc`, even when
|
|
182
|
+
no controller is online. It deletes a stopped box once the given hours have
|
|
183
|
+
passed since the later of:
|
|
184
|
+
|
|
185
|
+
- the box's stop, recorded in `user.raft.stopped-at` by `raft stop` and expiry;
|
|
186
|
+
- the policy change, recorded in `user.raft.delete-stopped-after.since`.
|
|
187
|
+
|
|
188
|
+
So a new or shorter policy never deletes a box sooner than its hours after the
|
|
189
|
+
change, including boxes that were stopped long before. Lengthening it or setting
|
|
190
|
+
it again restarts the grace period the same way.
|
|
191
|
+
|
|
192
|
+
| Box | What retention does |
|
|
193
|
+
| --- | --- |
|
|
194
|
+
| Running, including one whose TTL has not expired | Nothing |
|
|
195
|
+
| Stopped with `raft stop` or expiry | Deleted HOURS after the later of the stop and the policy change |
|
|
196
|
+
| Stopped another way: a poweroff inside the box, a native `incus stop`, a failed start or `raft recover` | The worker records the time it first sees the box stopped, and the clock starts then |
|
|
197
|
+
| Resumed before its deadline | Nothing; `resume` clears the stop time, and the next stop starts a new clock |
|
|
198
|
+
|
|
199
|
+
`raft list` shows when each stopped box will go, as `deleteAt`:
|
|
200
|
+
|
|
201
|
+
```sh title="CONTROLLER: Stopped boxes and their deletion times"
|
|
202
|
+
raft list --location lab | jq -r 'select(.deleteAt != "") | "\(.box) \(.deleteAt | tonumber | todate)"'
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Retention deletes the whole box. Keep anything you need with
|
|
206
|
+
[`raft backup`](./backup-recovery.mdx) before the deadline, or resume the box to
|
|
207
|
+
reset its clock. Snapshots live inside the box and are deleted with it.
|
|
208
|
+
|
|
209
|
+
Retention needs the current host worker. On a host deployed by an older release,
|
|
210
|
+
`--delete-stopped-after HOURS` fails with
|
|
211
|
+
`Host expiry worker differs from this CLI; deploy the current Raft checkout first`
|
|
212
|
+
and changes nothing; redeploy the host, see
|
|
213
|
+
[Upgrades and image changes](./upgrades.mdx). Clearing it with `never` works on
|
|
214
|
+
any worker.
|
|
215
|
+
|
|
216
|
+
`raft prune --older-than DAYS` remains a manual, previewed cleanup that works
|
|
217
|
+
with or without a retention policy.
|
|
218
|
+
|
|
154
219
|
## Audit trail
|
|
155
220
|
|
|
156
|
-
`raft policy` prints
|
|
157
|
-
`lab: required note fields none -> purpose,owner
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
221
|
+
`raft policy` prints each change, for example
|
|
222
|
+
`lab: required note fields none -> purpose,owner` or
|
|
223
|
+
`lab: delete stopped boxes after never -> 48 hours`. The host records the last
|
|
224
|
+
change of each policy in a `.changed` project key (UTC time, the SSH account and
|
|
225
|
+
the controller's self-reported user and hostname) and writes a `raft` entry to
|
|
226
|
+
the host journal. The worker adds a journal entry for every box retention
|
|
227
|
+
deletes:
|
|
161
228
|
|
|
162
|
-
```sh title="HOST: Review policy changes"
|
|
229
|
+
```sh title="HOST: Review policy changes and retention deletions"
|
|
163
230
|
sudo incus project get raft user.raft.require
|
|
164
231
|
sudo incus project get raft user.raft.require.changed
|
|
232
|
+
sudo incus project get raft user.raft.delete-stopped-after
|
|
233
|
+
sudo incus project get raft user.raft.delete-stopped-after.changed
|
|
165
234
|
sudo journalctl -t raft
|
|
166
235
|
```
|
|
167
236
|
|
|
168
237
|
## Limits
|
|
169
238
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
- Anyone with SSH and sudo on the host can change or remove
|
|
173
|
-
boxes with native `incus` commands that skip
|
|
174
|
-
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
239
|
+
Host policies are a guardrail for agents, not a security boundary:
|
|
240
|
+
|
|
241
|
+
- Anyone with SSH and sudo on the host can change or remove a policy, or create
|
|
242
|
+
boxes with native `incus` commands that skip the note policy.
|
|
243
|
+
- Retention trusts `user.raft.stopped-at`. The worker clears it when it sees a
|
|
244
|
+
box running, but a native `incus start` and stop between two worker runs (15
|
|
245
|
+
seconds apart) keeps the old stop time, so the box can be deleted on the next
|
|
246
|
+
run. Use `raft resume`, which clears the stop time.
|
|
247
|
+
- Controllers older than the release that added note policies do not check
|
|
248
|
+
them. Upgrade every controller that uses a host before relying on one. The
|
|
249
|
+
host worker enforces retention whatever the controller version.
|
|
250
|
+
- The controller identity in the `.changed` keys is reported by the controller
|
|
251
|
+
and is not verified. The SSH account comes from `sudo`.
|
|
178
252
|
- Raft checks that required fields are present and nonempty, not that their text
|
|
179
253
|
is accurate.
|
|
@@ -24,13 +24,19 @@ Raft has no option to disable automatic expiry.
|
|
|
24
24
|
| `resume --ttl SECONDS` | Starts the saved box with a new deadline | Only possible while the box still exists |
|
|
25
25
|
| `destroy` | Deletes the box and snapshots | Deletes the box and snapshots |
|
|
26
26
|
|
|
27
|
-
Use `raft new --disposable` for temporary work
|
|
27
|
+
Use `raft new --disposable` for temporary work, or
|
|
28
|
+
`raft config set disposable true` to make it the default on this controller;
|
|
29
|
+
`raft new --persistent` then keeps one box. A fork is persistent unless
|
|
28
30
|
`--disposable` is explicitly supplied; recovered boxes are persistent.
|
|
29
31
|
Back up work before stopping a disposable box.
|
|
30
32
|
|
|
31
33
|
Persistent stopped boxes still occupy storage. Raft does not cap how many boxes
|
|
32
34
|
a host keeps. `raft prune --older-than 7` previews old stopped boxes;
|
|
33
|
-
`--yes` applies deletion.
|
|
35
|
+
`--yes` applies deletion. To delete them automatically, set a host retention
|
|
36
|
+
policy: with `raft policy lab --delete-stopped-after 48`, the host deletes any
|
|
37
|
+
box left stopped for 48 hours, and `raft list` shows each one's `deleteAt`.
|
|
38
|
+
Resuming a box resets its clock. See
|
|
39
|
+
[Defaults and host policies](./config-policy.mdx#delete-boxes-left-stopped).
|
|
34
40
|
|
|
35
41
|
## Read and reset the deadline
|
|
36
42
|
|
|
@@ -19,7 +19,7 @@ Raft does not have a global `--json` option. Parse each command's actual format:
|
|
|
19
19
|
| --- | --- |
|
|
20
20
|
| `new`, `fork`, `recover` | One location-qualified handle |
|
|
21
21
|
| `exec --detach` | One `rfcmd-` job ID |
|
|
22
|
-
| `list` | JSONL: one object per box, with `box`, `state`, `stopAt`, `disk`, `note` |
|
|
22
|
+
| `list` | JSONL: one object per box, with `box`, `state`, `stopAt`, `deleteAt`, `disk`, `note` |
|
|
23
23
|
| `info` | One pretty-printed native Incus metadata JSON object |
|
|
24
24
|
| `usage` | One JSON object containing running resource counters |
|
|
25
25
|
| `capacity --json` | One JSON object per location (JSONL across multiple hosts) |
|
|
@@ -33,7 +33,9 @@ Raft does not have a global `--json` option. Parse each command's actual format:
|
|
|
33
33
|
| Other inspection/maintenance commands | Human-readable text or no output |
|
|
34
34
|
|
|
35
35
|
`list` emits nothing when there are no boxes. `stopAt` is the host Unix deadline
|
|
36
|
-
as a string, or an empty string when absent. `
|
|
36
|
+
as a string, or an empty string when absent. `deleteAt` is the Unix time the
|
|
37
|
+
location's retention policy deletes a stopped box, in the same form, or an empty
|
|
38
|
+
string. `disk` is `"shared"` or the box's
|
|
37
39
|
own disk size, such as `"20GiB"`. `note` is the box note object with
|
|
38
40
|
`purpose` and optional `owner` and `next`, or `null`. `info` uses native Incus keys rather
|
|
39
41
|
than a normalized Raft schema. `usage` includes `memoryBytes`, `memoryLimit`,
|
|
@@ -52,10 +54,11 @@ raft prune --location lab --older-than 7 | jq -r '.box'
|
|
|
52
54
|
|
|
53
55
|
`raft new` fills omitted options from the controller's
|
|
54
56
|
`~/.config/raft/config.json`, so the same script can create different boxes on
|
|
55
|
-
different controllers. Pass `--location`, `--ttl`, `--cpu`, `--memory
|
|
56
|
-
`--disk` in CI.
|
|
57
|
+
different controllers. Pass `--location`, `--ttl`, `--cpu`, `--memory`,
|
|
58
|
+
`--disk` and `--disposable` or `--persistent` in CI.
|
|
57
59
|
Hosts can also require box note fields; give `--purpose`, `--owner` and `--next`
|
|
58
|
-
so a policy cannot fail the job.
|
|
60
|
+
so a policy cannot fail the job. A host retention policy can delete a box left
|
|
61
|
+
stopped between jobs; see [Defaults and host policies](./config-policy.mdx).
|
|
59
62
|
|
|
60
63
|
Do not redirect `2>&1` into machine-readable output. Recovery reports its allocated
|
|
61
64
|
handle on stderr before import finishes; only a successful completion prints the
|
|
@@ -110,4 +110,4 @@ copy=$(raft fork "$box" --ttl 600 --cpu 2 --memory 4GiB)
|
|
|
110
110
|
raft resume "$box" --ttl 600 --memory 2GiB
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
-
Omitted CPU and RAM values keep the source limits; the disk never changes on fork or resume. Forks are persistent by default
|
|
113
|
+
Omitted CPU and RAM values keep the source limits; the disk never changes on fork or resume. Forks are persistent by default, even when `raft config` sets `disposable true`; add `--disposable` to delete a clone when it stops or expires.
|
|
@@ -33,8 +33,8 @@ raft doctor
|
|
|
33
33
|
|
|
34
34
|
An active expiration timer can still run an older worker. Installing or updating
|
|
35
35
|
the controller CLI does not deploy host code. `doctor` checks the same worker
|
|
36
|
-
protocol as disposable creation and retention
|
|
37
|
-
changing any boxes. It exits nonzero and names the location on a mismatch.
|
|
36
|
+
protocol as disposable creation, prune and setting a retention policy, without
|
|
37
|
+
running expiry or changing any boxes. It exits nonzero and names the location on a mismatch.
|
|
38
38
|
|
|
39
39
|
Review the host deployment changes, then deploy from the checkout matching your
|
|
40
40
|
CLI. Replace `lab` with the location reported by the diagnostic:
|
|
@@ -106,6 +106,23 @@ files and snapshots. Expired disposable containers are deleted, including their
|
|
|
106
106
|
snapshots. Use `raft gc --location lab` to check only one host. The timer normally
|
|
107
107
|
checks every 15 seconds; host outages, graceful shutdown and long locked
|
|
108
108
|
operations can delay expiry. See [Lifetime and persistent services](./long-running-services.mdx).
|
|
109
|
+
`raft gc` also applies the location's stopped-box retention.
|
|
110
|
+
|
|
111
|
+
### A stopped box disappeared
|
|
112
|
+
|
|
113
|
+
A host retention policy deletes boxes left stopped for its hours. Check the
|
|
114
|
+
policy and the host journal, which records every deletion:
|
|
115
|
+
|
|
116
|
+
```sh title="CONTROLLER: Check retention"
|
|
117
|
+
raft config | jq '.policy'
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```sh title="HOST: Find the deletion"
|
|
121
|
+
sudo journalctl -t raft | grep 'Deleted box'
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Deleted boxes cannot be recovered except from a `raft backup` archive. See
|
|
125
|
+
[Defaults and host policies](./config-policy.mdx#delete-boxes-left-stopped).
|
|
109
126
|
|
|
110
127
|
## Common error scenarios
|
|
111
128
|
|
package/docs/guides/upgrades.mdx
CHANGED
|
@@ -84,8 +84,10 @@ timer. It does not import an image or migrate guest files.
|
|
|
84
84
|
|
|
85
85
|
Do not bypass a conflicting-resource or storage-size error. Inspect the host and
|
|
86
86
|
preserve backups before explicit recovery; redeployment does not repair arbitrary
|
|
87
|
-
infrastructure drift. Disposable creation/fork
|
|
88
|
-
and fail if it differs from the CLI. That check
|
|
87
|
+
infrastructure drift. Disposable creation/fork, prune and setting a retention
|
|
88
|
+
policy check the worker protocol and fail if it differs from the CLI. That check
|
|
89
|
+
is not an automatic upgrade. Raft 0.6.0 raised the protocol for stopped-box
|
|
90
|
+
retention, so hosts deployed by an earlier release need this redeploy.
|
|
89
91
|
|
|
90
92
|
## Replace the image pin
|
|
91
93
|
|
package/docs/index.mdx
CHANGED
|
@@ -33,7 +33,7 @@ Stopping a workspace preserves files and packages on disk. Resuming resets its r
|
|
|
33
33
|
| Run detached commands with systemd logs | [Background jobs](./guides/background-jobs.mdx) |
|
|
34
34
|
| Manage deadlines and restart services after resume | [Lifetime and persistent services](./guides/long-running-services.mdx) |
|
|
35
35
|
| Automate the CLI and parse its output | [Scripting and CI](./guides/scripting-ci.mdx) |
|
|
36
|
-
| Set defaults for new boxes
|
|
36
|
+
| Set defaults for new boxes, require box note fields or delete boxes left stopped | [Defaults and host policies](./guides/config-policy.mdx) |
|
|
37
37
|
| Forward guest ports or open noVNC desktop | [Desktop and private services](./guides/desktop-services.mdx) |
|
|
38
38
|
| Capture disk state or clone workspaces | [Snapshots and forks](./guides/snapshots-forks.mdx) |
|
|
39
39
|
| Export portable backups across hosts | [Backup and recovery](./guides/backup-recovery.mdx) |
|
package/docs/operations.mdx
CHANGED
|
@@ -15,7 +15,7 @@ Install the CLI from npm on your Linux controller, confirm key-based SSH to the
|
|
|
15
15
|
host, then let `raft host add` do the remaining steps:
|
|
16
16
|
|
|
17
17
|
```sh title="CONTROLLER: Onboard a host"
|
|
18
|
-
npm install -g @microck/raft@0.
|
|
18
|
+
npm install -g @microck/raft@0.6.0
|
|
19
19
|
ssh ubuntu@192.0.2.10 true
|
|
20
20
|
raft host add lab --ssh ubuntu@192.0.2.10 --dry-run
|
|
21
21
|
raft host add lab --ssh ubuntu@192.0.2.10
|
|
@@ -62,7 +62,7 @@ sudo -n true && echo "Passwordless sudo is functional"
|
|
|
62
62
|
On your Linux controller with Python 3.11.8+, install the published CLI:
|
|
63
63
|
|
|
64
64
|
```sh title="CONTROLLER: Install published CLI"
|
|
65
|
-
npm install -g @microck/raft@0.
|
|
65
|
+
npm install -g @microck/raft@0.6.0
|
|
66
66
|
raft --version
|
|
67
67
|
```
|
|
68
68
|
|
|
@@ -70,7 +70,7 @@ Installation does not configure hosts or download images. Clone the matching
|
|
|
70
70
|
release to obtain the deployment scripts and example configuration:
|
|
71
71
|
|
|
72
72
|
```sh title="CONTROLLER: Obtain deployment scripts"
|
|
73
|
-
git clone --branch v0.
|
|
73
|
+
git clone --branch v0.6.0 https://github.com/Microck/raft.git raft
|
|
74
74
|
cd raft
|
|
75
75
|
```
|
|
76
76
|
|
|
@@ -161,16 +161,16 @@ Workspaces require an unprivileged Debian 13 development image matching the host
|
|
|
161
161
|
|
|
162
162
|
<Tab value="Download release image">
|
|
163
163
|
|
|
164
|
-
Download the release archive, checksum, and manifest from [Raft releases](https://github.com/Microck/raft/releases). These examples pin `v0.
|
|
164
|
+
Download the release archive, checksum, and manifest from [Raft releases](https://github.com/Microck/raft/releases). These examples pin `v0.6.0`, matching the CLI and deployment checkout above. The `sha256sum` check gates import so corrupted archives are rejected:
|
|
165
165
|
|
|
166
166
|
<Tabs items={["ARM64", "AMD64"]}>
|
|
167
167
|
|
|
168
168
|
<Tab value="ARM64">
|
|
169
169
|
|
|
170
170
|
```sh title="HOST (ARM64): Download, verify, and import release image"
|
|
171
|
-
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.
|
|
172
|
-
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.
|
|
173
|
-
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.
|
|
171
|
+
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.6.0/raft-dev-arm64.tar.gz
|
|
172
|
+
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.6.0/SHA256SUMS
|
|
173
|
+
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.6.0/manifest-arm64.json
|
|
174
174
|
|
|
175
175
|
sha256sum --ignore-missing -c SHA256SUMS && \
|
|
176
176
|
sudo incus --project raft image import ./raft-dev-arm64.tar.gz --alias raft-dev
|
|
@@ -181,9 +181,9 @@ sha256sum --ignore-missing -c SHA256SUMS && \
|
|
|
181
181
|
<Tab value="AMD64">
|
|
182
182
|
|
|
183
183
|
```sh title="HOST (AMD64): Download, verify, and import release image"
|
|
184
|
-
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.
|
|
185
|
-
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.
|
|
186
|
-
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.
|
|
184
|
+
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.6.0/raft-dev-amd64.tar.gz
|
|
185
|
+
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.6.0/SHA256SUMS
|
|
186
|
+
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.6.0/manifest-amd64.json
|
|
187
187
|
|
|
188
188
|
sha256sum --ignore-missing -c SHA256SUMS && \
|
|
189
189
|
sudo incus --project raft image import ./raft-dev-amd64.tar.gz --alias raft-dev
|
package/docs/reference/cli.md
CHANGED
|
@@ -16,7 +16,7 @@ Output is command-specific; see [Scripting and CI](../guides/scripting-ci.mdx).
|
|
|
16
16
|
|
|
17
17
|
At most once a day, after a command finishes, Raft checks GitHub for a newer
|
|
18
18
|
release and prints one line on stderr, such as
|
|
19
|
-
`raft 0.
|
|
19
|
+
`raft 0.7.0 is available (you have 0.6.0). Run: raft self-update`. It never
|
|
20
20
|
installs anything or changes stdout, and network failures stay silent. See
|
|
21
21
|
[`self-update`](#self-update) to turn it off.
|
|
22
22
|
|
|
@@ -44,7 +44,7 @@ Workspaces are referenced by location-qualified handles formatted as `<location>
|
|
|
44
44
|
|
|
45
45
|
Create and launch a new workspace container.
|
|
46
46
|
|
|
47
|
-
`raft new [--location LOCATION] [--ttl TTL] [--cpu N] [--memory SIZE] [--disk SIZE] [--disposable] [--from LOCATION:BOX/SNAPSHOT] [--purpose TEXT [--owner TEXT] [--next TEXT]]`
|
|
47
|
+
`raft new [--location LOCATION] [--ttl TTL] [--cpu N] [--memory SIZE] [--disk SIZE] [--disposable | --persistent] [--from LOCATION:BOX/SNAPSHOT] [--purpose TEXT [--owner TEXT] [--next TEXT]]`
|
|
48
48
|
|
|
49
49
|
- `--location LOCATION`: Target host location in `~/.config/raft/incus.json`. Built-in default: first configured location.
|
|
50
50
|
- `--ttl TTL`: Lifetime in seconds before automatic stop (`60` to `2592000`). Built-in default: `600`.
|
|
@@ -57,7 +57,8 @@ then the built-in default. With `--from`, configured `cpu`, `memory` and `disk`
|
|
|
57
57
|
defaults do not apply: the box keeps the snapshot's size unless a flag overrides
|
|
58
58
|
it, and `--disk shared` is refused.
|
|
59
59
|
|
|
60
|
-
- `--disposable`: Delete the box and snapshots on stop or expiry.
|
|
60
|
+
- `--disposable`: Delete the box and snapshots on stop or expiry. Built-in default: persistent.
|
|
61
|
+
- `--persistent`: Keep the box and its disk on stop or expiry, overriding a configured `disposable` default. Cannot be combined with `--disposable`.
|
|
61
62
|
- `--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
63
|
- `--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.
|
|
63
64
|
|
|
@@ -222,7 +223,7 @@ Clone a stopped workspace into an independent container on the same host and sta
|
|
|
222
223
|
- `--ttl TTL`: Lifetime in seconds for the clone. Required.
|
|
223
224
|
- `--purpose TEXT`, `--owner TEXT`, `--next TEXT`: Replace the clone's whole note. Without them the clone keeps the source note.
|
|
224
225
|
|
|
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
|
|
226
|
+
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 and of a configured `disposable` default.
|
|
226
227
|
The note the clone ends up with must satisfy the host [policy](#policy).
|
|
227
228
|
|
|
228
229
|
Returns the qualified handle of the clone.
|
|
@@ -357,8 +358,12 @@ Show or change the controller defaults for `new`.
|
|
|
357
358
|
`raft config set KEY VALUE`
|
|
358
359
|
`raft config unset KEY`
|
|
359
360
|
|
|
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.
|
|
361
|
+
- `KEY`: One of `location`, `ttl`, `cpu`, `memory`, `disk`, `disposable`.
|
|
362
|
+
- `VALUE`: Accepted exactly when the same text is accepted by the matching `new` flag. A `location` must be configured. `disposable` takes `true` or `false`.
|
|
363
|
+
|
|
364
|
+
`disposable true` makes `new` create disposable boxes, including `new --from`,
|
|
365
|
+
because a snapshot carries no lifecycle; `--persistent` keeps one box. `fork`
|
|
366
|
+
ignores it: a fork is persistent unless it gets `--disposable`.
|
|
362
367
|
|
|
363
368
|
`cpu`, `memory` and `disk` size only boxes created from the image. `new --from`
|
|
364
369
|
and `fork` keep the source's CPU, RAM and disk; only explicit flags change them.
|
|
@@ -373,29 +378,48 @@ file makes `new` fail with the file path and key; `set` or `unset` that key to
|
|
|
373
378
|
repair it.
|
|
374
379
|
|
|
375
380
|
`raft config` prints one JSON object: `file`, `defaults` (each key's `value` and
|
|
376
|
-
`source`, `config` or `built-in`) and `policy
|
|
377
|
-
|
|
378
|
-
|
|
381
|
+
`source`, `config` or `built-in`) and `policy`. For each location, `policy` has
|
|
382
|
+
`require` (the required note fields), `deleteStoppedAfterHours` (`null` without
|
|
383
|
+
retention), `deleteStoppedAfterSince` (when retention was set, Unix seconds) and
|
|
384
|
+
the last change records `requireChanged` and `deleteStoppedAfterChanged`. It
|
|
385
|
+
contacts every configured host to read policies.
|
|
386
|
+
See [Defaults and host policies](../guides/config-policy.mdx).
|
|
379
387
|
|
|
380
388
|
### `policy`
|
|
381
389
|
|
|
382
|
-
Set or clear the box note fields
|
|
390
|
+
Set or clear a location's host policies: the box note fields it requires and
|
|
391
|
+
how long it keeps stopped boxes.
|
|
392
|
+
|
|
393
|
+
`raft policy LOCATION [--require FIELDS|none] [--delete-stopped-after HOURS|never]`
|
|
383
394
|
|
|
384
|
-
`
|
|
385
|
-
`
|
|
395
|
+
- `--require FIELDS`: Comma-separated fields from `purpose`, `owner`, `next`. Must include `purpose`. `--require none` stops requiring fields.
|
|
396
|
+
- `--delete-stopped-after HOURS`: Delete boxes that stay stopped for HOURS, a positive whole number, with their snapshots. `--delete-stopped-after never` keeps stopped boxes.
|
|
386
397
|
|
|
387
|
-
|
|
388
|
-
|
|
398
|
+
Give either option or both; each changes only its own policy. Both values are
|
|
399
|
+
checked before the host changes.
|
|
389
400
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
|
401
|
+
Policies are stored on the host in the Incus project `raft`, so they apply to
|
|
402
|
+
every controller. The command prints each change and the host records the time,
|
|
403
|
+
SSH account and controller in a `.changed` key beside the policy and in the host
|
|
395
404
|
journal (`journalctl -t raft`).
|
|
396
405
|
|
|
397
|
-
|
|
398
|
-
|
|
406
|
+
The note policy lives in `user.raft.require`. `new`, `fork`, `recover` and
|
|
407
|
+
`note` check it on the controller for an actionable error, then again in the
|
|
408
|
+
host transaction that writes the note.
|
|
409
|
+
|
|
410
|
+
Retention lives in `user.raft.delete-stopped-after`, with the time it was set in
|
|
411
|
+
`user.raft.delete-stopped-after.since`. The host expiry worker deletes a stopped
|
|
412
|
+
box once HOURS have passed since the later of its stop and that time, so a new
|
|
413
|
+
or shorter policy never deletes a box sooner than HOURS after the change. The
|
|
414
|
+
worker runs every 15 seconds and on [`gc`](#gc), logs each deletion to the host
|
|
415
|
+
journal and never touches running boxes. A stopped box with no recorded stop
|
|
416
|
+
time, such as one powered off from inside, stopped with native `incus` or
|
|
417
|
+
recovered from a backup, starts its clock when the worker first sees it.
|
|
418
|
+
[`list`](#list) shows each box's `deleteAt`. Setting retention needs the current
|
|
419
|
+
host worker; on an older one it fails with redeploy guidance.
|
|
420
|
+
|
|
421
|
+
Policies are guardrails for agents, not a security boundary: SSH and sudo on the
|
|
422
|
+
host can change or bypass them, and older controllers do not check note policies.
|
|
399
423
|
|
|
400
424
|
## Host inspection and maintenance commands
|
|
401
425
|
|
|
@@ -424,7 +448,9 @@ List existing workspace containers across configured hosts.
|
|
|
424
448
|
|
|
425
449
|
`raft list [--location LOCATION]`
|
|
426
450
|
|
|
427
|
-
Prints one JSON object per box (JSONL), with `box`, `state`, `stopAt`, `disk` and `note` fields.
|
|
451
|
+
Prints one JSON object per box (JSONL), with `box`, `state`, `stopAt`, `deleteAt`, `disk` and `note` fields.
|
|
452
|
+
`deleteAt` is the Unix time when the location's [retention](#policy) deletes a
|
|
453
|
+
stopped box, as a string like `stopAt`, or `""` when no deletion is due.
|
|
428
454
|
`disk` is `"shared"` for the shared pool or the own disk's size, such as `"20GiB"`.
|
|
429
455
|
`note` is the box note object, or `null` when the box has none.
|
|
430
456
|
An empty inventory prints no records. See [Scripting and CI](../guides/scripting-ci.mdx)
|
|
@@ -432,7 +458,7 @@ for output formats, exit codes, readiness, and failure handling.
|
|
|
432
458
|
|
|
433
459
|
### `gc`
|
|
434
460
|
|
|
435
|
-
Enforce deadlines immediately. Persistent boxes stop; disposable boxes are deleted.
|
|
461
|
+
Enforce deadlines and retention immediately. Persistent boxes stop; disposable boxes are deleted; stopped boxes past the location's [retention](#policy) are deleted.
|
|
436
462
|
|
|
437
463
|
`raft gc [--location LOCATION]`
|
|
438
464
|
|
|
@@ -442,9 +468,9 @@ Preview deletion of persistent stopped boxes older than the selected number of d
|
|
|
442
468
|
|
|
443
469
|
`raft prune --older-than DAYS [--location LOCATION] [--yes]`
|
|
444
470
|
|
|
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,
|
|
471
|
+
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, or from the host worker when it first sees a box stopped another way, never from box creation. For automatic deletion, set a [retention policy](#policy).
|
|
446
472
|
|
|
447
|
-
Disposable creation/fork and
|
|
473
|
+
Disposable creation/fork, prune and setting retention 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
474
|
|
|
449
475
|
## CLI maintenance commands
|
|
450
476
|
|
|
@@ -80,7 +80,8 @@ controller and is separate from the location inventory:
|
|
|
80
80
|
"ttl": 3600,
|
|
81
81
|
"cpu": 2,
|
|
82
82
|
"memory": "4GiB",
|
|
83
|
-
"disk": "20GiB"
|
|
83
|
+
"disk": "20GiB",
|
|
84
|
+
"disposable": true
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
```
|
|
@@ -92,15 +93,18 @@ controller and is separate from the location inventory:
|
|
|
92
93
|
| `cpu` | A positive whole number | `1` |
|
|
93
94
|
| `memory` | Whole GiB, e.g. `16GiB` | `2GiB` |
|
|
94
95
|
| `disk` | Whole GiB for an own box disk, or `shared` | `shared` |
|
|
96
|
+
| `disposable` | `true` or `false` | `false` (persistent) |
|
|
95
97
|
|
|
96
|
-
Explicit flags win over these values
|
|
97
|
-
created from the image: `new --from`
|
|
98
|
-
|
|
98
|
+
Explicit flags win over these values; `--persistent` overrides `disposable`.
|
|
99
|
+
`cpu`, `memory` and `disk` size only boxes created from the image: `new --from`
|
|
100
|
+
and `fork` keep the source's size unless a flag overrides it. `disposable`
|
|
101
|
+
applies to `new` and `new --from`, never to `fork`. A `disk` default needs the `raft-disks` pool on the target
|
|
99
102
|
host, like `--disk`. Unknown keys and invalid values make
|
|
100
103
|
`raft new` fail with the file path and key. Edit the file with
|
|
101
104
|
`raft config set KEY VALUE` and `raft config unset KEY`, and inspect it with
|
|
102
|
-
`raft config`. Required box note fields
|
|
103
|
-
|
|
105
|
+
`raft config`. Required box note fields and stopped-box retention are host
|
|
106
|
+
policies, not part of this file; see
|
|
107
|
+
[Defaults and host policies](../guides/config-policy.mdx).
|
|
104
108
|
|
|
105
109
|
## SSH configuration and connection behavior
|
|
106
110
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Raft 0.6.0
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Raft 0.6.0
|
|
6
|
+
|
|
7
|
+
- **Disposable by default.** `raft config set disposable true` makes `raft new` create disposable boxes, which are deleted with their snapshots when they stop or expire. `raft new --persistent` keeps one box anyway, and `--disposable` still works the other way. The default also applies to `new --from`; a fork stays persistent unless you pass `--disposable`. `raft config` shows the setting.
|
|
8
|
+
- **Delete boxes left stopped.** `raft policy LOCATION --delete-stopped-after 48` makes a host delete boxes, with their snapshots, once they have been stopped for 48 hours. The host applies it every 15 seconds and on `raft gc`, even with no controller online, logs each deletion to its journal (`journalctl -t raft`) and never touches running boxes. A new or shorter policy never deletes a box sooner than its hours after the change, and resuming a box resets its clock. `raft list` shows each stopped box's `deleteAt`. `--delete-stopped-after never` turns it off.
|
|
9
|
+
- **Stop times for every stopped box.** The host now records a stop time for boxes stopped outside `raft stop`, such as a poweroff inside the box, a native `incus stop` or a recovered backup, when it first sees them. `raft prune` can therefore select them later; before, it skipped them forever.
|
|
10
|
+
- **`raft policy` changes.** `--require` and `--delete-stopped-after` can be set together or alone, and each changes only its own policy. `--require none` replaces `--clear`, which is gone. In `raft config`, each location's `changed` field is now `requireChanged`, beside the new `deleteStoppedAfterHours`, `deleteStoppedAfterSince` and `deleteStoppedAfterChanged`.
|
|
11
|
+
|
|
12
|
+
Install on Linux with Python 3.11.8+ using `npm install -g @microck/raft@0.6.0`, or install the attached Python wheel; existing installs can run `raft self-update`.
|
|
13
|
+
|
|
14
|
+
Redeploy each host with this release's `deploy/deploy.py`. The host expiry worker changed, and until a host is redeployed `raft doctor` reports it, while `new --disposable` (including a disposable default), `fork --disposable`, `raft prune` and `raft policy --delete-stopped-after HOURS` fail on it with redeploy guidance. Redeploying changes nothing else on the host and sets no retention policy.
|
|
15
|
+
|
|
16
|
+
ARM64 and AMD64 development images accompany this release. Verify `SHA256SUMS` before importing an image for your host architecture.
|
package/docs/verification.mdx
CHANGED
|
@@ -58,7 +58,7 @@ The [final runtime validation](https://github.com/Microck/raft/actions/runs/3754
|
|
|
58
58
|
|
|
59
59
|
## New workspace controls
|
|
60
60
|
|
|
61
|
-
`python3 deploy/verify-features.py --location lab` verifies `doctor` readiness against the installed worker, recursive trees and unsafe-archive rejection, templates from a running source, snapshot deletion, CPU/RAM overrides, disposable timer deletion,
|
|
61
|
+
`python3 deploy/verify-features.py --location lab` verifies `doctor` readiness against the installed worker, recursive trees and unsafe-archive rejection, templates from a running source, snapshot deletion, CPU/RAM overrides, disposable timer deletion, prune preview/apply, host note policies for `new`, `fork`, `note` and `recover`, and stopped-box retention: it sets a 1-hour policy, backdates one stopped box and the policy start, and checks that the next timer run deletes only that box, records the stop time of a box stopped natively and logs to the journal. It sets policies on the host and clears them at the end, and uses only its own fixtures. Every live suite runs the CLI in a private `HOME` holding only `incus.json`, so controller defaults such as `disposable true` never change their boxes. Run the archive, controller-default, policy-message and host worker retention checks without `--location` to avoid allocating boxes; the worker check runs the real worker against a fake `incus` on `PATH`.
|
|
62
62
|
|
|
63
63
|
The live suite also holds a controller stdin pipe open while running real remote
|
|
64
64
|
readers through both captured and streamed lifecycle transactions. Both must
|