@microck/raft 0.2.0 → 0.2.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/README.md +5 -0
- package/deploy/verify-features.py +58 -0
- package/docs/contract.md +2 -2
- package/docs/first-box.mdx +4 -0
- package/docs/guides/background-jobs.mdx +3 -0
- package/docs/guides/desktop-services.mdx +4 -1
- package/docs/guides/long-running-services.mdx +109 -0
- package/docs/guides/meta.json +4 -0
- package/docs/guides/project-setup.mdx +112 -0
- package/docs/guides/scripting-ci.mdx +133 -0
- package/docs/guides/troubleshooting.mdx +28 -3
- package/docs/guides/upgrades.mdx +113 -0
- package/docs/index.mdx +4 -0
- package/docs/operations.mdx +27 -12
- package/docs/reference/capacity-limits.mdx +10 -1
- package/docs/reference/cli.md +13 -4
- package/docs/reference/configuration.mdx +1 -1
- package/docs/releases/0.2.1.md +14 -0
- package/docs/verification.mdx +8 -2
- package/package.json +1 -1
- package/raft.py +7 -1
package/README.md
CHANGED
|
@@ -76,6 +76,11 @@ see the task guides for [background jobs](docs/guides/background-jobs.mdx),
|
|
|
76
76
|
[backup and recovery](docs/guides/backup-recovery.mdx)
|
|
77
77
|
and [Docker](docs/guides/docker.mdx).
|
|
78
78
|
|
|
79
|
+
See [project setup and templates](docs/guides/project-setup.mdx),
|
|
80
|
+
[persistent services](docs/guides/long-running-services.mdx),
|
|
81
|
+
[scripting and CI](docs/guides/scripting-ci.mdx), and
|
|
82
|
+
[upgrades](docs/guides/upgrades.mdx) for complete operator workflows.
|
|
83
|
+
|
|
79
84
|
stop the source box before taking a snapshot, restoring one or making a fork.
|
|
80
85
|
stopping a box ends its jobs without saving process memory.
|
|
81
86
|
|
|
@@ -157,6 +157,62 @@ def verify_archives():
|
|
|
157
157
|
)
|
|
158
158
|
|
|
159
159
|
|
|
160
|
+
def verify_readiness(location, expect_stale=False):
|
|
161
|
+
# Isolate the selected host without losing the controller's verified SSH config.
|
|
162
|
+
with tempfile.TemporaryDirectory(prefix="raft-doctor-test-") as work:
|
|
163
|
+
home = Path(work)
|
|
164
|
+
(home / ".ssh").symlink_to(Path.home() / ".ssh", target_is_directory=True)
|
|
165
|
+
config = home / ".config/raft/incus.json"
|
|
166
|
+
config.parent.mkdir(parents=True)
|
|
167
|
+
config.write_text(json.dumps({location: settings(location)}))
|
|
168
|
+
response = live.raft("doctor", check=False, env={**os.environ, "HOME": str(home)})
|
|
169
|
+
assert location + ": " in response.stdout, response
|
|
170
|
+
if expect_stale:
|
|
171
|
+
assert response.returncode == 1, response
|
|
172
|
+
assert (
|
|
173
|
+
f"raft: {location}: Host expiry worker differs from this CLI; "
|
|
174
|
+
"deploy the current Raft checkout first"
|
|
175
|
+
) in response.stderr, response
|
|
176
|
+
else:
|
|
177
|
+
assert response.returncode == 0, response
|
|
178
|
+
assert "saved boxes; system containers (shared host kernel)" in response.stdout, response
|
|
179
|
+
print(f"{location}: doctor {'rejected stale worker' if expect_stale else 'passed'}", flush=True)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def verify_transaction_stdin(location):
|
|
183
|
+
program = """import sys, tempfile
|
|
184
|
+
from raft import transaction
|
|
185
|
+
assert transaction(sys.argv[1], 'cat') == ''
|
|
186
|
+
with tempfile.TemporaryFile() as output:
|
|
187
|
+
transaction(sys.argv[1], 'cat', stdout=output)
|
|
188
|
+
output.seek(0)
|
|
189
|
+
assert output.read() == b''
|
|
190
|
+
"""
|
|
191
|
+
with subprocess.Popen(
|
|
192
|
+
[sys.executable, "-c", program, location],
|
|
193
|
+
cwd=ROOT,
|
|
194
|
+
stdin=subprocess.PIPE,
|
|
195
|
+
stdout=subprocess.PIPE,
|
|
196
|
+
stderr=subprocess.PIPE,
|
|
197
|
+
) as process:
|
|
198
|
+
try:
|
|
199
|
+
process.stdin.write(b"must-not-reach-the-host\n")
|
|
200
|
+
process.stdin.flush()
|
|
201
|
+
process.wait(timeout=30)
|
|
202
|
+
finally:
|
|
203
|
+
# Release even a regressed remote reader before leaving this test.
|
|
204
|
+
process.stdin.close()
|
|
205
|
+
try:
|
|
206
|
+
process.wait(timeout=30)
|
|
207
|
+
except subprocess.TimeoutExpired:
|
|
208
|
+
process.kill()
|
|
209
|
+
process.wait()
|
|
210
|
+
raise
|
|
211
|
+
assert process.returncode == 0, process.stderr.read().decode()
|
|
212
|
+
assert transaction(location, "printf lock-released") == "lock-released"
|
|
213
|
+
print(f"{location}: open stdin ignored and lifecycle lock released", flush=True)
|
|
214
|
+
|
|
215
|
+
|
|
160
216
|
def verify(location, isolated_worker=False):
|
|
161
217
|
if inventory(location):
|
|
162
218
|
raise RuntimeError(
|
|
@@ -236,6 +292,8 @@ def verify(location, isolated_worker=False):
|
|
|
236
292
|
return any(box["name"] == parse_handle(handle)[1] for box in inventory(location))
|
|
237
293
|
|
|
238
294
|
try:
|
|
295
|
+
verify_readiness(location, expect_stale=isolated_worker)
|
|
296
|
+
verify_transaction_stdin(location)
|
|
239
297
|
box = create("--memory", "1GiB")
|
|
240
298
|
live.wait_running(box)
|
|
241
299
|
_, name = parse_handle(box)
|
package/docs/contract.md
CHANGED
|
@@ -35,7 +35,7 @@ Raft manages persistent unprivileged native-architecture Incus system containers
|
|
|
35
35
|
| Lifecycle defaults | Defaults to first configured location, 1 CPU, 2 GiB RAM, and 600-second lifetime. Supported choices are 1 or 2 CPUs, 1/2/4 GiB RAM, and 60 to 2,592,000 seconds (30 days) TTL. |
|
|
36
36
|
| Admission limit | Hard ceiling of four saved boxes per host, including stopped boxes. |
|
|
37
37
|
| TTL calculation | Running TTL begins after launch completes, using the host clock. Cold image preparation does not consume TTL. |
|
|
38
|
-
| Host locking | Creation, lifecycle transitions, and expiry are serialized by host file lock (`/run/lock/raft-incus.lock`). Native Incus admin calls bypass CLI policy. |
|
|
38
|
+
| Host locking | Creation, lifecycle transitions, and expiry are serialized by host file lock (`/run/lock/raft-incus.lock`). Noninteractive transactions close controller stdin so commands cannot wait for piped input while holding this lock. Exec, SSH, and file-transfer paths remain unchanged. Piped input to `raft exec` is unsupported. Native Incus admin calls bypass CLI policy. |
|
|
39
39
|
|
|
40
40
|
## Capacity and admission inspection
|
|
41
41
|
|
|
@@ -51,7 +51,7 @@ Raft manages persistent unprivileged native-architecture Incus system containers
|
|
|
51
51
|
|
|
52
52
|
- **Stop and resume**: Stop terminates guest processes and preserves disk contents; stopped boxes retain disk but do not reserve guest RAM. Resume starts the container anew with an explicit TTL without restoring process memory.
|
|
53
53
|
- **Lifetime extension**: `raft extend --ttl` resets a running box's deadline from current host time without restarting; stopped boxes require `raft resume`.
|
|
54
|
-
- **Host worker contract**:
|
|
54
|
+
- **Host worker contract**: `doctor`, disposable creation/fork, and retention cleanup require the current worker protocol. `doctor` checks it without running expiry or changing boxes, and reports the failing location. A mismatch fails before executing deletion policy or creating a disposable box; deploy the current checkout explicitly. Installing a new CLI does not update host workers.
|
|
55
55
|
- **Disposable lifecycle**: `new --disposable` opts a box into deletion on stop or TTL expiry, including its snapshots. Clones and recovered boxes default to persistent; fork accepts an explicit `--disposable`.
|
|
56
56
|
- **Retention cleanup**: `prune --older-than DAYS` previews persistent stopped boxes with a recorded stop timestamp. `--yes` deletes matching boxes under the host lifecycle lock. Running boxes and boxes without a recorded stop time are excluded. No automatic persistent-box deletion is enabled.
|
|
57
57
|
- **Resizing**: Resume and fork accept optional supported CPU/RAM overrides, preserving omitted values. Resume validates stopped state under the lock before changing sizing.
|
package/docs/first-box.mdx
CHANGED
|
@@ -71,6 +71,9 @@ raft destroy "$box"
|
|
|
71
71
|
|
|
72
72
|
## Next steps
|
|
73
73
|
|
|
74
|
+
- [Project setup and templates](./guides/project-setup.mdx): Prepare a project and reuse its dependencies.
|
|
75
|
+
- [Lifetime and persistent services](./guides/long-running-services.mdx): Manage deadlines and restart applications after resume.
|
|
76
|
+
- [Scripting and CI](./guides/scripting-ci.mdx): Parse CLI output and handle failures.
|
|
74
77
|
- [Docker in workspaces](./guides/docker.mdx): Run nested containers and build images.
|
|
75
78
|
- [File transfer](./guides/files.mdx): Upload and download files over SSH.
|
|
76
79
|
- [Background jobs](./guides/background-jobs.mdx): Run detached commands under systemd.
|
|
@@ -78,5 +81,6 @@ raft destroy "$box"
|
|
|
78
81
|
- [Snapshots and forks](./guides/snapshots-forks.mdx): Capture disk states and create same-host clones.
|
|
79
82
|
- [Backup and recovery](./guides/backup-recovery.mdx): Export portable archives and restore across hosts.
|
|
80
83
|
- [Troubleshooting](./guides/troubleshooting.mdx): Diagnose common errors and inspect host services.
|
|
84
|
+
- [Upgrades and image changes](./guides/upgrades.mdx): Update the controller, hosts, and image pins.
|
|
81
85
|
|
|
82
86
|
For temporary work, create with `--disposable`. Stopping or expiry then deletes the box and its snapshots. For persistent boxes, `raft prune --older-than 7` previews old stopped workspaces; add `--yes` only after reviewing the candidates.
|
|
@@ -90,3 +90,6 @@ raft extend "$box" --ttl 1800
|
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
`raft extend` resets the container expiration deadline to `now + TTL` seconds using the host clock. It does not add time to the existing deadline.
|
|
93
|
+
|
|
94
|
+
For deadline inspection and an enabled systemd service that starts again after
|
|
95
|
+
resume or fork, see [Lifetime and persistent services](./long-running-services.mdx).
|
|
@@ -66,7 +66,10 @@ Start the desktop and establish a private tunnel:
|
|
|
66
66
|
raft desktop "$box" --local 6080
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
`raft desktop`
|
|
69
|
+
`raft desktop` requests startup of `raft-desktop.service` inside the container if
|
|
70
|
+
needed and opens a tunnel to local port `6080`. The service waits for Xvfb before
|
|
71
|
+
launching its desktop clients. The CLI does not poll noVNC readiness; if the first
|
|
72
|
+
browser request fails during startup, retry after the service initializes.
|
|
70
73
|
|
|
71
74
|
Open the client in your browser:
|
|
72
75
|
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Lifetime and persistent services
|
|
3
|
+
description: Inspect deadlines, manage disposable and persistent boxes, and restart applications after resume.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Lifetime and persistent services
|
|
7
|
+
|
|
8
|
+
A box's lifetime controls when the host stops or deletes it. Activity, an open
|
|
9
|
+
terminal, and detached jobs do not extend that deadline automatically.
|
|
10
|
+
|
|
11
|
+
## Choose a lifetime and retention policy
|
|
12
|
+
|
|
13
|
+
```sh title="CONTROLLER: Create a persistent box"
|
|
14
|
+
box=$(raft new --location lab --ttl 3600)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
TTL is in seconds: 60 to 2,592,000 (30 days), with a creation default of 600.
|
|
18
|
+
The host sets the deadline after launch completes, using its own clock.
|
|
19
|
+
Raft has no option to disable automatic expiry.
|
|
20
|
+
|
|
21
|
+
| Operation | Persistent box (default) | Disposable box |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| `stop` or TTL expiry | Stops processes; keeps disk and snapshots | Deletes the box and snapshots |
|
|
24
|
+
| `resume --ttl SECONDS` | Starts the saved box with a new deadline | Only possible while the box still exists |
|
|
25
|
+
| `destroy` | Deletes the box and snapshots | Deletes the box and snapshots |
|
|
26
|
+
|
|
27
|
+
Use `raft new --disposable` for temporary work. A fork is persistent unless
|
|
28
|
+
`--disposable` is explicitly supplied; recovered boxes are persistent.
|
|
29
|
+
Back up work before stopping a disposable box.
|
|
30
|
+
|
|
31
|
+
Persistent stopped boxes still occupy a saved slot and storage. Each host allows
|
|
32
|
+
four saved boxes. `raft prune --older-than 7` previews old stopped boxes;
|
|
33
|
+
`--yes` applies deletion. There is no automatic persistent-box retention cleanup.
|
|
34
|
+
|
|
35
|
+
## Read and reset the deadline
|
|
36
|
+
|
|
37
|
+
Select a running handle from `raft list`. These examples use controller `jq`:
|
|
38
|
+
|
|
39
|
+
```sh title="CONTROLLER: Inspect and extend a running box"
|
|
40
|
+
raft list
|
|
41
|
+
box="lab:rf-a1b2c3d4e5f60718" # Replace with your actual handle
|
|
42
|
+
raft info "$box" | jq '{status, expires: .config["user.raft.expires"]}'
|
|
43
|
+
raft extend "$box" --ttl 7200
|
|
44
|
+
raft info "$box" | jq '{status, expires: .config["user.raft.expires"]}'
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`user.raft.expires` is a Unix timestamp encoded as a string, not a remaining
|
|
48
|
+
duration. `raft list` exposes it as `stopAt`; it may be empty on stopped boxes.
|
|
49
|
+
`extend` sets `now + TTL`, rather than adding time to the old deadline.
|
|
50
|
+
It requires a running box. Start a stopped box with `raft resume "$box" --ttl 7200`.
|
|
51
|
+
|
|
52
|
+
The host timer checks every 15 seconds. Graceful shutdown, a host outage, or a
|
|
53
|
+
long transaction holding the lifecycle lock can delay expiry. `raft gc --location lab`
|
|
54
|
+
runs an immediate deadline check. A deadline is not a guarantee that a job will
|
|
55
|
+
finish before shutdown; extend before starting work that needs more time.
|
|
56
|
+
|
|
57
|
+
## Restart a service after resume
|
|
58
|
+
|
|
59
|
+
One-off detached commands do not survive a stop. To start an application again
|
|
60
|
+
after resume or fork, save a systemd unit on disk and enable it. This starts a new
|
|
61
|
+
process; it does not recover process memory or an interrupted request.
|
|
62
|
+
|
|
63
|
+
The following example serves `/workspace` as the guest `developer` user. Use a
|
|
64
|
+
persistent running box and ensure files the application needs are readable by
|
|
65
|
+
that user. For an actual application, replace `ExecStart` and the working directory.
|
|
66
|
+
|
|
67
|
+
```sh title="CONTROLLER: Install and enable an example service"
|
|
68
|
+
raft exec "$box" -- bash -lc 'cat > /etc/systemd/system/workspace-web.service <<"UNIT"
|
|
69
|
+
[Unit]
|
|
70
|
+
Description=Workspace HTTP example
|
|
71
|
+
After=network.target
|
|
72
|
+
|
|
73
|
+
[Service]
|
|
74
|
+
User=developer
|
|
75
|
+
WorkingDirectory=/workspace
|
|
76
|
+
ExecStart=/usr/bin/python3 -m http.server 8080 --bind 0.0.0.0
|
|
77
|
+
Restart=on-failure
|
|
78
|
+
RestartSec=2
|
|
79
|
+
|
|
80
|
+
[Install]
|
|
81
|
+
WantedBy=multi-user.target
|
|
82
|
+
UNIT
|
|
83
|
+
systemctl daemon-reload
|
|
84
|
+
systemctl enable --now workspace-web.service'
|
|
85
|
+
raft exec "$box" -- systemctl status workspace-web.service --no-pager
|
|
86
|
+
raft exec "$box" -- journalctl -u workspace-web.service -n 30 --no-pager
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Open `raft forward "$box" --remote 8080 --local 8080` and visit
|
|
90
|
+
`http://127.0.0.1:8080`. The tunnel runs in the foreground; close it with `Ctrl+C`.
|
|
91
|
+
|
|
92
|
+
```sh title="CONTROLLER: Check service startup after resume"
|
|
93
|
+
raft stop "$box"
|
|
94
|
+
raft resume "$box" --ttl 3600
|
|
95
|
+
raft exec "$box" -- systemctl is-active workspace-web.service
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Reopen the tunnel after resume. A fork or snapshot template copies the enabled
|
|
99
|
+
unit too, so its new box starts its own service. Application secrets on disk are
|
|
100
|
+
also copied; see [Project setup and templates](./project-setup.mdx).
|
|
101
|
+
|
|
102
|
+
To disable the example, run:
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
raft exec "$box" -- systemctl disable --now workspace-web.service
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
See [Background jobs](./background-jobs.mdx) for one-off tasks and
|
|
109
|
+
[Desktop and private services](./desktop-services.mdx) for tunnel behavior.
|
package/docs/guides/meta.json
CHANGED
|
@@ -3,12 +3,16 @@
|
|
|
3
3
|
"collapsible": true,
|
|
4
4
|
"defaultOpen": false,
|
|
5
5
|
"pages": [
|
|
6
|
+
"project-setup",
|
|
6
7
|
"docker",
|
|
7
8
|
"files",
|
|
8
9
|
"background-jobs",
|
|
10
|
+
"long-running-services",
|
|
11
|
+
"scripting-ci",
|
|
9
12
|
"desktop-services",
|
|
10
13
|
"snapshots-forks",
|
|
11
14
|
"backup-recovery",
|
|
15
|
+
"upgrades",
|
|
12
16
|
"troubleshooting"
|
|
13
17
|
]
|
|
14
18
|
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Project setup and templates
|
|
3
|
+
description: Upload or clone a project, run setup scripts, and create reusable same-host snapshot templates.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Project setup and templates
|
|
7
|
+
|
|
8
|
+
Raft starts a clean development image. Prepare a project with ordinary guest
|
|
9
|
+
commands, then save a snapshot to reuse installed dependencies. There is no
|
|
10
|
+
managed repository registry, environment registry, setup hook, or secret-injection
|
|
11
|
+
service. Commands run as guest root in `/workspace` unless you choose another user.
|
|
12
|
+
|
|
13
|
+
## Put a project in the box
|
|
14
|
+
|
|
15
|
+
Create a persistent box after [host setup](../operations.mdx):
|
|
16
|
+
|
|
17
|
+
```sh title="CONTROLLER: Create a project box"
|
|
18
|
+
box=$(raft new --location lab --ttl 3600)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Upload a local project, excluding credentials and files you do not want copied.
|
|
22
|
+
The example assumes `./my-project` contains your project and `setup.sh`:
|
|
23
|
+
|
|
24
|
+
```sh title="CONTROLLER: Upload local project"
|
|
25
|
+
raft upload "$box" ./my-project /workspace/my-project --recursive
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The destination directory must not exist. For a public repository, an alternative
|
|
29
|
+
is guest `git clone`; replace the example URL with your repository:
|
|
30
|
+
|
|
31
|
+
```sh title="CONTROLLER: Clone instead of uploading"
|
|
32
|
+
raft exec "$box" -- git clone https://github.com/OWNER/REPOSITORY.git /workspace/my-project
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Choose one method. Private repositories require authentication configured inside
|
|
36
|
+
the guest; Raft does not forward your controller's GitHub login or SSH agent.
|
|
37
|
+
The clean image does not bundle coding agents or their credentials. Install and
|
|
38
|
+
authenticate any agent inside the box using that tool's own instructions.
|
|
39
|
+
|
|
40
|
+
## Run setup explicitly
|
|
41
|
+
|
|
42
|
+
Use `bash -lc` for directory changes or shell syntax:
|
|
43
|
+
|
|
44
|
+
```sh title="CONTROLLER: Run project setup"
|
|
45
|
+
raft exec "$box" -- bash -lc 'cd /workspace/my-project && bash ./setup.sh'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Make setup scripts safe to rerun. Raft does not execute them automatically on
|
|
49
|
+
create, resume, or fork. A failing synchronous command returns its exit code;
|
|
50
|
+
see [Scripting and CI](./scripting-ci.mdx).
|
|
51
|
+
|
|
52
|
+
For slow setup, use a detached job and inspect its result:
|
|
53
|
+
|
|
54
|
+
```sh title="CONTROLLER: Run slow setup instead of synchronous setup"
|
|
55
|
+
job=$(raft exec "$box" --detach -- bash -lc 'cd /workspace/my-project && bash ./setup.sh')
|
|
56
|
+
raft status "$box" "$job"
|
|
57
|
+
raft logs "$box" "$job"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Wait for successful completion before making a template. Extending the lifetime
|
|
61
|
+
may be necessary; a detached job does not prevent expiry.
|
|
62
|
+
|
|
63
|
+
## Supply application configuration
|
|
64
|
+
|
|
65
|
+
For an application that reads a local `.env` file, upload it explicitly and set
|
|
66
|
+
permissions for the application's user. This example assumes a controller file
|
|
67
|
+
`./app.env` and an application running as `developer`:
|
|
68
|
+
|
|
69
|
+
```sh title="CONTROLLER: Install a private application configuration file"
|
|
70
|
+
raft upload "$box" ./app.env /workspace/my-project/.env
|
|
71
|
+
raft exec "$box" -- chown developer:developer /workspace/my-project/.env
|
|
72
|
+
raft exec "$box" -- chmod 600 /workspace/my-project/.env
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The application must load that file; upload does not export environment variables.
|
|
76
|
+
If running the whole project as `developer`, adjust project ownership separately.
|
|
77
|
+
Do not place secrets in command arguments, committed files, or logs. Host journals
|
|
78
|
+
can capture guest command lines. Raft does not rotate or scrub credentials.
|
|
79
|
+
|
|
80
|
+
## Save and reuse a prepared template
|
|
81
|
+
|
|
82
|
+
Finish setup, remove credentials that should not be inherited, and stop the box:
|
|
83
|
+
|
|
84
|
+
```sh title="CONTROLLER: Save the prepared filesystem"
|
|
85
|
+
raft stop "$box"
|
|
86
|
+
raft snapshot "$box" prepared
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Create another box from the snapshot:
|
|
90
|
+
|
|
91
|
+
```sh title="CONTROLLER: Start a copy with explicit resources"
|
|
92
|
+
copy=$(raft new --from "$box/prepared" --ttl 3600 --cpu 2 --memory 4GiB)
|
|
93
|
+
raft exec "$copy" -- ls -la /workspace/my-project
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The template stays on its source host. Its source box can be running when you
|
|
97
|
+
copy the saved snapshot. `new --from` uses normal creation defaults when sizing
|
|
98
|
+
is omitted: 1 CPU and 2 GiB RAM. The new box has its own handle, MAC address,
|
|
99
|
+
deadline, and saved slot.
|
|
100
|
+
|
|
101
|
+
The snapshot includes packages, project files, credentials, and enabled services.
|
|
102
|
+
It does not include process memory. Changing the original after snapshot creation
|
|
103
|
+
does not update the saved template. Keep its source box and snapshot until you
|
|
104
|
+
no longer need to create copies; destroying the source deletes its snapshots.
|
|
105
|
+
|
|
106
|
+
To copy the stopped box's current filesystem instead, use
|
|
107
|
+
`raft fork "$box" --ttl 3600`. Fork inherits omitted CPU/RAM limits and does not
|
|
108
|
+
copy the source snapshots. See [Snapshots and forks](./snapshots-forks.mdx).
|
|
109
|
+
|
|
110
|
+
For a different host, use [Backup and recovery](./backup-recovery.mdx) with matching
|
|
111
|
+
CPU architecture. For services that start on resume, see
|
|
112
|
+
[Lifetime and persistent services](./long-running-services.mdx).
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Scripting and CI
|
|
3
|
+
description: Parse CLI output, preserve command exit codes, wait for readiness, and handle interrupted operations.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Scripting and CI
|
|
7
|
+
|
|
8
|
+
Raft is a controller CLI using host SSH. A Linux CI runner needs Python 3.11.8+,
|
|
9
|
+
OpenSSH, the installed CLI, host inventory, verified SSH host keys, batch
|
|
10
|
+
authentication, and hosts with passwordless sudo. No Raft HTTP API, SDK, or API key
|
|
11
|
+
exists. Give access only to trusted jobs: the configured SSH account has host sudo
|
|
12
|
+
and controls all boxes on that host.
|
|
13
|
+
|
|
14
|
+
## Output formats
|
|
15
|
+
|
|
16
|
+
Raft does not have a global `--json` option. Parse each command's actual format:
|
|
17
|
+
|
|
18
|
+
| Command | stdout |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| `new`, `fork`, `recover` | One location-qualified handle |
|
|
21
|
+
| `exec --detach` | One `rfcmd-` job ID |
|
|
22
|
+
| `list` | JSONL: one object per box, with `box`, `state`, `stopAt` |
|
|
23
|
+
| `info` | One pretty-printed native Incus metadata JSON object |
|
|
24
|
+
| `usage` | One JSON object containing running resource counters |
|
|
25
|
+
| `limits --json` | One JSON object per location (JSONL across multiple hosts) |
|
|
26
|
+
| `prune` | JSONL candidates or deleted records with `box`, `action` |
|
|
27
|
+
| `status` | systemd `ActiveState`, `SubState`, `ExecMainStatus` property lines |
|
|
28
|
+
| `logs` | Guest journal text |
|
|
29
|
+
| `exec` | Payload stdout/stderr |
|
|
30
|
+
| Other inspection/maintenance commands | Human-readable text or no output |
|
|
31
|
+
|
|
32
|
+
`list` emits nothing when there are no boxes. `stopAt` is the host Unix deadline
|
|
33
|
+
as a string, or an empty string when absent. `info` uses native Incus keys rather
|
|
34
|
+
than a normalized Raft schema. `usage` includes `memoryBytes`, `memoryLimit`,
|
|
35
|
+
`cpuTime`, `effectiveCpus`, `filesystemUsedBytes`, `filesystemAvailableBytes`, and
|
|
36
|
+
`filesystemScope`; disk values describe the shared pool, not an individual box.
|
|
37
|
+
See [Capacity limits](../reference/capacity-limits.mdx) for capacity calculations.
|
|
38
|
+
|
|
39
|
+
These examples require `jq` on the controller:
|
|
40
|
+
|
|
41
|
+
```sh title="CONTROLLER: Parse records without mixing stderr"
|
|
42
|
+
raft list --location lab | jq -r 'select(.state == "Running") | .box'
|
|
43
|
+
raft limits --location lab --json | jq '{location, savedSlots, warnings}'
|
|
44
|
+
raft prune --location lab --older-than 7 | jq -r '.box'
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Do not redirect `2>&1` into machine-readable output. Recovery reports its allocated
|
|
48
|
+
handle on stderr before import finishes; only a successful completion prints the
|
|
49
|
+
final handle on stdout.
|
|
50
|
+
|
|
51
|
+
## Exit codes and job results
|
|
52
|
+
|
|
53
|
+
| Situation | Exit code |
|
|
54
|
+
| --- | --- |
|
|
55
|
+
| Successful control operation | `0` |
|
|
56
|
+
| Synchronous `exec` | Transport/command return code; preserves payload exit status when transport succeeds |
|
|
57
|
+
| Recognized runtime/configuration failure | `1`, with `raft: ...` on stderr |
|
|
58
|
+
| Argument parsing failure | `2`, with argparse diagnostics on stderr |
|
|
59
|
+
| Caught keyboard interruption | `130` |
|
|
60
|
+
|
|
61
|
+
SSH transport failures may return SSH's status rather than the payload's result.
|
|
62
|
+
The foreground tunnel replaces the CLI process with SSH and uses SSH's exit code.
|
|
63
|
+
A zero exit from `status` means the query succeeded; read `ExecMainStatus` for the
|
|
64
|
+
job result and `ActiveState`/`SubState` for whether it finished. Do not interpret a
|
|
65
|
+
running job's initial `ExecMainStatus=0` as completion. See
|
|
66
|
+
[Background jobs](./background-jobs.mdx).
|
|
67
|
+
|
|
68
|
+
## Create, wait, run, and stop
|
|
69
|
+
|
|
70
|
+
`new` returns after Incus starts the box, before every guest service is necessarily
|
|
71
|
+
ready. Probe a harmless command before running setup. This Bash example waits up
|
|
72
|
+
to about a minute plus transport time, executes an uploaded project check, and
|
|
73
|
+
attempts to stop the persistent box on exit. It assumes `./my-project/check.sh`
|
|
74
|
+
exists and the configured host has a free saved slot:
|
|
75
|
+
|
|
76
|
+
```bash title="CONTROLLER or trusted Linux CI runner"
|
|
77
|
+
set -euo pipefail
|
|
78
|
+
box=""
|
|
79
|
+
cleanup() {
|
|
80
|
+
result=$?
|
|
81
|
+
trap - EXIT
|
|
82
|
+
if [ -n "$box" ]; then
|
|
83
|
+
if ! raft stop "$box"; then
|
|
84
|
+
echo "Cleanup failed; inspect $box" >&2
|
|
85
|
+
[ "$result" -ne 0 ] || result=1
|
|
86
|
+
fi
|
|
87
|
+
fi
|
|
88
|
+
exit "$result"
|
|
89
|
+
}
|
|
90
|
+
trap cleanup EXIT
|
|
91
|
+
|
|
92
|
+
box=$(raft new --location lab --ttl 3600)
|
|
93
|
+
printf 'Created %s\n' "$box" >&2
|
|
94
|
+
ready=false
|
|
95
|
+
for attempt in {1..30}; do
|
|
96
|
+
if raft exec "$box" -- true; then
|
|
97
|
+
ready=true
|
|
98
|
+
break
|
|
99
|
+
fi
|
|
100
|
+
sleep 2
|
|
101
|
+
done
|
|
102
|
+
if [ "$ready" != true ]; then
|
|
103
|
+
echo "Guest did not become command-ready: $box" >&2
|
|
104
|
+
exit 1
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
raft upload "$box" ./my-project /workspace/my-project --recursive
|
|
108
|
+
raft exec "$box" -- bash -lc 'cd /workspace/my-project && bash ./check.sh'
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The stopped box remains available for inspection and still consumes a saved slot.
|
|
112
|
+
Destroy it after collecting needed files, or preview old stopped boxes with prune.
|
|
113
|
+
For disposable CI boxes, deletion on stop/expiry removes artifacts and snapshots;
|
|
114
|
+
download results before cleanup. A new box or a retry still consumes a saved slot.
|
|
115
|
+
|
|
116
|
+
Command readiness does not mean Docker or a web application is ready. Probe the
|
|
117
|
+
specific dependency (`docker info`, an HTTP health endpoint, or systemd status)
|
|
118
|
+
with its own bounded wait. Extend the deadline before work exceeding the current
|
|
119
|
+
lifetime. This example's retries are only for the harmless readiness probe.
|
|
120
|
+
|
|
121
|
+
## Interrupted operations and retries
|
|
122
|
+
|
|
123
|
+
Raft does not automatically retry commands or provide idempotency keys. If SSH
|
|
124
|
+
drops during creation, resume, import, or execution, the remote operation may have
|
|
125
|
+
already happened. Inspect `raft list`, `raft info`, guest logs, and the host before
|
|
126
|
+
repeating an operation that creates boxes or changes data. A failed create can
|
|
127
|
+
leave a saved box even when no handle reached stdout.
|
|
128
|
+
|
|
129
|
+
For an interrupted recovery, inspect the handle printed on stderr and follow
|
|
130
|
+
[Host crash during recovery](./troubleshooting.mdx#host-crash-during-recovery).
|
|
131
|
+
For uncertain command execution, check its effects before rerunning it; use
|
|
132
|
+
idempotent setup scripts where possible. Do not treat a lost connection as proof
|
|
133
|
+
of deletion or of a command not having run.
|
|
@@ -19,15 +19,36 @@ raft doctor
|
|
|
19
19
|
|
|
20
20
|
`raft doctor` connects to each host over SSH and reports:
|
|
21
21
|
- Incus version running in project `raft`
|
|
22
|
+
- Compatibility of the installed expiry worker with the CLI
|
|
22
23
|
- Active status of host systemd services (`incus`, `raft-expire.timer`, `raft-network.service`)
|
|
23
24
|
- Host root filesystem disk usage (`df -h /`)
|
|
24
25
|
- Status and usage of the `raft-data` Btrfs pool
|
|
25
26
|
- Count of existing saved containers
|
|
26
27
|
|
|
27
28
|
<Callout type="info" title="Doctor scope">
|
|
28
|
-
`raft doctor` reports runtime daemon and storage status. It does not validate image fingerprints, inspect nftables bridge rules, enforce admission caps, or emit 5 GiB low-disk warnings.
|
|
29
|
+
`raft doctor` reports runtime daemon, expiry-worker compatibility, and storage status. It does not validate image fingerprints, inspect nftables bridge rules, enforce admission caps, or emit 5 GiB low-disk warnings.
|
|
29
30
|
</Callout>
|
|
30
31
|
|
|
32
|
+
### Expiry worker differs from the CLI
|
|
33
|
+
|
|
34
|
+
An active expiration timer can still run an older worker. Installing or updating
|
|
35
|
+
the controller CLI does not deploy host code. `doctor` checks the same worker
|
|
36
|
+
protocol as disposable creation and retention cleanup, without running expiry or
|
|
37
|
+
changing any boxes. It exits nonzero and names the location on a mismatch.
|
|
38
|
+
|
|
39
|
+
Review the host deployment changes, then deploy from the checkout matching your
|
|
40
|
+
CLI. Replace `lab` with the location reported by the diagnostic:
|
|
41
|
+
|
|
42
|
+
```sh title="CONTROLLER: Explicitly update a reviewed host deployment"
|
|
43
|
+
python3 deploy/deploy.py --location lab
|
|
44
|
+
raft doctor
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Deployment updates host infrastructure as well as the expiry worker. Do not run
|
|
48
|
+
it on a shared host without authorization. Do not remove the compatibility check
|
|
49
|
+
or mark an old worker as current; older workers may retain expired disposable
|
|
50
|
+
boxes instead of deleting them.
|
|
51
|
+
|
|
31
52
|
### Listing active and stopped containers
|
|
32
53
|
|
|
33
54
|
Inspect workspace states across locations:
|
|
@@ -74,13 +95,17 @@ Host system journals can capture command lines, arguments, and environment varia
|
|
|
74
95
|
|
|
75
96
|
### Manually triggering garbage collection
|
|
76
97
|
|
|
77
|
-
The host timer
|
|
98
|
+
The host timer enforces expiry every 15 seconds. Trigger an immediate expiration run from the controller:
|
|
78
99
|
|
|
79
100
|
```sh title="CONTROLLER: Run expiration workers manually"
|
|
80
101
|
raft gc
|
|
81
102
|
```
|
|
82
103
|
|
|
83
|
-
Expired containers transition from `Running` to `Stopped
|
|
104
|
+
Expired persistent containers transition from `Running` to `Stopped` and retain
|
|
105
|
+
files and snapshots. Expired disposable containers are deleted, including their
|
|
106
|
+
snapshots. Use `raft gc --location lab` to check only one host. The timer normally
|
|
107
|
+
checks every 15 seconds; host outages, graceful shutdown and long locked
|
|
108
|
+
operations can delay expiry. See [Lifetime and persistent services](./long-running-services.mdx).
|
|
84
109
|
|
|
85
110
|
## Common error scenarios
|
|
86
111
|
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Upgrades and image changes
|
|
3
|
+
description: Update the controller CLI and host worker, replace image fingerprints, and understand existing-box behavior.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Upgrades and image changes
|
|
7
|
+
|
|
8
|
+
The controller CLI, host deployment scripts, and workspace image are separate
|
|
9
|
+
parts of Raft. Updating one does not automatically update the others.
|
|
10
|
+
|
|
11
|
+
| Component | Update method | Effect on existing boxes |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| Controller CLI | Install the chosen npm or Python release | Changes controller commands; does not rewrite guest files |
|
|
14
|
+
| Host worker and firewall | Run the chosen release's deployer per host | Replaces host policy code and reapplies filtering |
|
|
15
|
+
| Pinned development image | Import an image and change the configuration fingerprint | Used for future image-based `new`; existing boxes keep their files |
|
|
16
|
+
|
|
17
|
+
## Choose a release and prepare
|
|
18
|
+
|
|
19
|
+
Review [release notes](https://github.com/Microck/raft/releases) and use matching
|
|
20
|
+
CLI and deployment sources. The examples below pin `v0.2.0`; replace that version
|
|
21
|
+
consistently when adopting another release.
|
|
22
|
+
|
|
23
|
+
```sh title="CONTROLLER: Inspect the current installation"
|
|
24
|
+
raft --version
|
|
25
|
+
raft doctor
|
|
26
|
+
raft list
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Save a copy of your controller inventory. Export important persistent boxes using
|
|
30
|
+
[Backup and recovery](./backup-recovery.mdx) before host changes. Backup requires a
|
|
31
|
+
stopped box. Stopping a disposable box deletes it, so copy needed files out before
|
|
32
|
+
stopping one. Schedule redeployment when interruptions are acceptable.
|
|
33
|
+
|
|
34
|
+
## Update the controller
|
|
35
|
+
|
|
36
|
+
For an npm installation:
|
|
37
|
+
|
|
38
|
+
```sh title="CONTROLLER: Install a pinned CLI release"
|
|
39
|
+
npm install -g @microck/raft@0.2.0
|
|
40
|
+
raft --version
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
For a Python installation, download the wheel and `SHA256SUMS` from the same
|
|
44
|
+
release, verify the checksum, then install it with `uv`:
|
|
45
|
+
|
|
46
|
+
```sh title="CONTROLLER: Install a verified release wheel instead of npm"
|
|
47
|
+
sha256sum --ignore-missing -c SHA256SUMS
|
|
48
|
+
uv tool install --force ./raft-0.2.0-py3-none-any.whl
|
|
49
|
+
raft --version
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Proceed only if checksum verification succeeds. Choose one installation method
|
|
53
|
+
and check which executable your shell uses with `command -v raft`.
|
|
54
|
+
|
|
55
|
+
For an editable source installation, updating the checkout changes the installed
|
|
56
|
+
tool's source. Keep uncommitted work separate and run `uv sync --locked` for the
|
|
57
|
+
chosen checkout. Raft has no `self-update` command.
|
|
58
|
+
|
|
59
|
+
## Redeploy each host
|
|
60
|
+
|
|
61
|
+
Obtain a separate checkout of the matching release:
|
|
62
|
+
|
|
63
|
+
```sh title="CONTROLLER: Obtain and deploy matching host code"
|
|
64
|
+
git clone --branch v0.2.0 https://github.com/Microck/raft.git raft-v0.2.0
|
|
65
|
+
cd raft-v0.2.0
|
|
66
|
+
python3 deploy/deploy.py --location lab
|
|
67
|
+
raft doctor
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Repeat for each configured location. The deployer uses your existing
|
|
71
|
+
`~/.config/raft/incus.json`, verifies owned project/pool/network/profile state,
|
|
72
|
+
replaces host scripts and units, reapplies firewall rules, and starts the expiry
|
|
73
|
+
timer. It does not import an image or migrate guest files.
|
|
74
|
+
|
|
75
|
+
Do not bypass a conflicting-resource or storage-size error. Inspect the host and
|
|
76
|
+
preserve backups before explicit recovery; redeployment does not repair arbitrary
|
|
77
|
+
infrastructure drift. Disposable creation/fork and prune check the worker protocol
|
|
78
|
+
and fail if it differs from the CLI. That check is not an automatic upgrade.
|
|
79
|
+
|
|
80
|
+
## Replace the image pin
|
|
81
|
+
|
|
82
|
+
Follow [Acquire a native workspace image](../operations.mdx#acquire-a-native-workspace-image)
|
|
83
|
+
to download and verify or build the image for each host's architecture. Retrieve
|
|
84
|
+
its full fingerprint and replace only that location's `image` field in the
|
|
85
|
+
controller inventory. Keep the previous fingerprint if you may need it again.
|
|
86
|
+
|
|
87
|
+
Changing the pin affects ordinary `raft new` calls. Resume retains the existing
|
|
88
|
+
filesystem; forks and `new --from` use their source filesystem, not the new image.
|
|
89
|
+
Install guest updates explicitly inside an existing box, or create a new box and
|
|
90
|
+
transfer the project and needed data. Recovering an old backup also restores that
|
|
91
|
+
backup's guest filesystem rather than rebuilding it from the new image.
|
|
92
|
+
|
|
93
|
+
## Verify the update
|
|
94
|
+
|
|
95
|
+
```sh title="CONTROLLER: Smoke-check a new persistent box"
|
|
96
|
+
box=$(raft new --location lab --ttl 600)
|
|
97
|
+
raft exec "$box" -- node --version
|
|
98
|
+
raft exec "$box" -- docker info
|
|
99
|
+
raft stop "$box"
|
|
100
|
+
raft resume "$box" --ttl 600
|
|
101
|
+
raft exec "$box" -- ls /workspace
|
|
102
|
+
raft destroy "$box"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Creation can return before guest services are ready. Use the readiness pattern in
|
|
106
|
+
[Scripting and CI](./scripting-ci.mdx) if an immediate check fails during startup.
|
|
107
|
+
For dedicated test infrastructure, [Verification](../verification.mdx) describes
|
|
108
|
+
the real-host suites and their effects.
|
|
109
|
+
|
|
110
|
+
To revert an image choice for future boxes, restore the previous fingerprint
|
|
111
|
+
while that image remains imported. This does not revert existing boxes. Raft has
|
|
112
|
+
no automatic migration or rollback of host policy or guest packages; review the
|
|
113
|
+
chosen release and backups before downgrading those components.
|
package/docs/index.mdx
CHANGED
|
@@ -27,10 +27,14 @@ Stopping a workspace preserves files and packages on disk. Resuming resets its r
|
|
|
27
27
|
|
|
28
28
|
| Task | Guide |
|
|
29
29
|
| :--- | :--- |
|
|
30
|
+
| Prepare a project and reuse its installed dependencies | [Project setup and templates](./guides/project-setup.mdx) |
|
|
30
31
|
| Run nested containers and build images | [Docker in workspaces](./guides/docker.mdx) |
|
|
31
32
|
| Transfer files between controller and workspace | [File transfer](./guides/files.mdx) |
|
|
32
33
|
| Run detached commands with systemd logs | [Background jobs](./guides/background-jobs.mdx) |
|
|
34
|
+
| Manage deadlines and restart services after resume | [Lifetime and persistent services](./guides/long-running-services.mdx) |
|
|
35
|
+
| Automate the CLI and parse its output | [Scripting and CI](./guides/scripting-ci.mdx) |
|
|
33
36
|
| Forward guest ports or open noVNC desktop | [Desktop and private services](./guides/desktop-services.mdx) |
|
|
34
37
|
| Capture disk state or clone workspaces | [Snapshots and forks](./guides/snapshots-forks.mdx) |
|
|
35
38
|
| Export portable backups across hosts | [Backup and recovery](./guides/backup-recovery.mdx) |
|
|
39
|
+
| Update the CLI, host worker, and workspace image | [Upgrades and image changes](./guides/upgrades.mdx) |
|
|
36
40
|
| Inspect host services and diagnose errors | [Troubleshooting](./guides/troubleshooting.mdx) |
|
package/docs/operations.mdx
CHANGED
|
@@ -33,18 +33,31 @@ sudo -n true && echo "Passwordless sudo is functional"
|
|
|
33
33
|
|
|
34
34
|
<Step>
|
|
35
35
|
|
|
36
|
-
###
|
|
36
|
+
### Install the CLI and obtain deployment scripts
|
|
37
37
|
|
|
38
|
-
On your controller
|
|
38
|
+
On your Linux controller with Python 3.11.8+, install the published CLI:
|
|
39
39
|
|
|
40
|
-
```sh title="CONTROLLER:
|
|
41
|
-
|
|
40
|
+
```sh title="CONTROLLER: Install published CLI"
|
|
41
|
+
npm install -g @microck/raft@0.2.0
|
|
42
|
+
raft --version
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Installation does not configure hosts or download images. Clone the matching
|
|
46
|
+
release to obtain the deployment scripts and example configuration:
|
|
47
|
+
|
|
48
|
+
```sh title="CONTROLLER: Obtain deployment scripts"
|
|
49
|
+
git clone --branch v0.2.0 https://github.com/Microck/raft.git raft
|
|
42
50
|
cd raft
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Alternatively, install from that checkout using `uv` instead of npm:
|
|
54
|
+
|
|
55
|
+
```sh title="CONTROLLER: Clone repository and install CLI"
|
|
43
56
|
uv sync --locked
|
|
44
57
|
uv tool install --editable .
|
|
45
58
|
```
|
|
46
59
|
|
|
47
|
-
|
|
60
|
+
For the `uv` installation, add `~/.local/bin` to `PATH` and verify the CLI:
|
|
48
61
|
|
|
49
62
|
```sh title="CONTROLLER: Verify CLI installation"
|
|
50
63
|
export PATH="$HOME/.local/bin:$PATH"
|
|
@@ -122,16 +135,16 @@ Workspaces require an unprivileged Debian 13 development image matching the host
|
|
|
122
135
|
|
|
123
136
|
<Tab value="Download release image">
|
|
124
137
|
|
|
125
|
-
Download the release archive, checksum, and manifest from [Raft releases](https://github.com/Microck/raft/releases). The `sha256sum` check gates import so corrupted archives are rejected:
|
|
138
|
+
Download the release archive, checksum, and manifest from [Raft releases](https://github.com/Microck/raft/releases). These examples pin `v0.2.0`, matching the CLI and deployment checkout above. The `sha256sum` check gates import so corrupted archives are rejected:
|
|
126
139
|
|
|
127
140
|
<Tabs items={["ARM64", "AMD64"]}>
|
|
128
141
|
|
|
129
142
|
<Tab value="ARM64">
|
|
130
143
|
|
|
131
144
|
```sh title="HOST (ARM64): Download, verify, and import release image"
|
|
132
|
-
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.
|
|
133
|
-
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.
|
|
134
|
-
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.
|
|
145
|
+
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.2.0/raft-dev-arm64.tar.gz
|
|
146
|
+
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.2.0/SHA256SUMS
|
|
147
|
+
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.2.0/manifest-arm64.json
|
|
135
148
|
|
|
136
149
|
sha256sum --ignore-missing -c SHA256SUMS && \
|
|
137
150
|
sudo incus --project raft image import ./raft-dev-arm64.tar.gz --alias raft-dev
|
|
@@ -142,9 +155,9 @@ sha256sum --ignore-missing -c SHA256SUMS && \
|
|
|
142
155
|
<Tab value="AMD64">
|
|
143
156
|
|
|
144
157
|
```sh title="HOST (AMD64): Download, verify, and import release image"
|
|
145
|
-
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.
|
|
146
|
-
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.
|
|
147
|
-
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.
|
|
158
|
+
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.2.0/raft-dev-amd64.tar.gz
|
|
159
|
+
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.2.0/SHA256SUMS
|
|
160
|
+
curl -fsSL -O https://github.com/Microck/raft/releases/download/v0.2.0/manifest-amd64.json
|
|
148
161
|
|
|
149
162
|
sha256sum --ignore-missing -c SHA256SUMS && \
|
|
150
163
|
sudo incus --project raft image import ./raft-dev-amd64.tar.gz --alias raft-dev
|
|
@@ -222,3 +235,5 @@ Check that Incus, the network service, and the expiration timer are active. For
|
|
|
222
235
|
## Next step
|
|
223
236
|
|
|
224
237
|
Proceed to [First working box](./first-box.mdx) to launch, verify, and manage your first workspace.
|
|
238
|
+
|
|
239
|
+
For later releases, follow [Upgrades and image changes](./guides/upgrades.mdx).
|
|
@@ -50,7 +50,16 @@ Select CPU and memory limits when launching with `raft new`:
|
|
|
50
50
|
- `--cpu`: `1` or `2` shared vCPUs managed by Incus (default: `1`).
|
|
51
51
|
- `--memory`: `1GiB`, `2GiB`, or `4GiB` cgroup limit (default: `2GiB`).
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
Change supported CPU/RAM limits when resuming a stopped box or creating a fork:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
raft resume "$box" --ttl 1800 --cpu 2 --memory 4GiB
|
|
57
|
+
copy=$(raft fork "$box" --ttl 1800 --memory 2GiB)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Resume requires a stopped box; fork requires a stopped source. Omitted limits
|
|
61
|
+
stay unchanged on resume and inherit the source limits on fork. Live resizing is
|
|
62
|
+
not exposed by the CLI. See [Snapshots and forks](../guides/snapshots-forks.mdx).
|
|
54
63
|
|
|
55
64
|
## Host resource reserves and active states
|
|
56
65
|
|
package/docs/reference/cli.md
CHANGED
|
@@ -10,6 +10,10 @@ Command-line reference for the `raft` controller tool.
|
|
|
10
10
|
Syntax:
|
|
11
11
|
`raft <subcommand> [options] [arguments]`
|
|
12
12
|
|
|
13
|
+
`raft --help` lists commands, `raft <subcommand> --help` lists its arguments,
|
|
14
|
+
and `raft --version` prints the installed version without loading configuration.
|
|
15
|
+
Output is command-specific; see [Scripting and CI](../guides/scripting-ci.mdx).
|
|
16
|
+
|
|
13
17
|
Workspaces are referenced by location-qualified handles formatted as `<location>:<instance-name>` (for example `lab:rf-a1b2c3d4e5f60718`).
|
|
14
18
|
|
|
15
19
|
## Command index
|
|
@@ -31,7 +35,7 @@ Workspaces are referenced by location-qualified handles formatted as `<location>
|
|
|
31
35
|
|
|
32
36
|
Create and launch a new workspace container.
|
|
33
37
|
|
|
34
|
-
`raft new [--location LOCATION] [--ttl TTL] [--cpu {1,2}] [--memory {1GiB,2GiB,4GiB}]`
|
|
38
|
+
`raft new [--location LOCATION] [--ttl TTL] [--cpu {1,2}] [--memory {1GiB,2GiB,4GiB}] [--disposable] [--from LOCATION:BOX/SNAPSHOT]`
|
|
35
39
|
|
|
36
40
|
- `--location LOCATION`: Target host location in `~/.config/raft/incus.json`. Defaults to first configured location.
|
|
37
41
|
- `--ttl TTL`: Lifetime in seconds before automatic stop (`60` to `2592000`). Default: `600`.
|
|
@@ -59,6 +63,9 @@ Start a stopped workspace with a renewed lifetime deadline.
|
|
|
59
63
|
|
|
60
64
|
- `--ttl TTL`: Lifetime in seconds from resumption (`60` to `2592000`). Required.
|
|
61
65
|
|
|
66
|
+
CPU/RAM overrides on resume apply before startup. Omitted values stay unchanged.
|
|
67
|
+
A running box is rejected before changing its limits.
|
|
68
|
+
|
|
62
69
|
### `extend`
|
|
63
70
|
|
|
64
71
|
Reset the expiration deadline for a running workspace without restarting it.
|
|
@@ -75,8 +82,6 @@ Permanently delete a workspace container and its snapshots.
|
|
|
75
82
|
|
|
76
83
|
Stops the container if running, removes all associated Btrfs snapshots, and releases host storage.
|
|
77
84
|
|
|
78
|
-
CPU/RAM overrides on resume apply before startup. Omitted values stay unchanged. A running box is rejected before changing its limits.
|
|
79
|
-
|
|
80
85
|
## Execution and inspection commands
|
|
81
86
|
|
|
82
87
|
### `exec`
|
|
@@ -252,7 +257,7 @@ Inspect host resource capacity, active allocations, and recommended workspace li
|
|
|
252
257
|
|
|
253
258
|
### `doctor`
|
|
254
259
|
|
|
255
|
-
Inspect host connectivity, Incus version, active systemd units (`incus`, `raft-expire.timer`, `raft-network.service`), root filesystem usage, storage pool status, and saved box count across configured hosts.
|
|
260
|
+
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.
|
|
256
261
|
|
|
257
262
|
`raft doctor`
|
|
258
263
|
|
|
@@ -262,6 +267,10 @@ List existing workspace containers across configured hosts.
|
|
|
262
267
|
|
|
263
268
|
`raft list [--location LOCATION]`
|
|
264
269
|
|
|
270
|
+
Prints one JSON object per box (JSONL), with `box`, `state`, and `stopAt` fields.
|
|
271
|
+
An empty inventory prints no records. See [Scripting and CI](../guides/scripting-ci.mdx)
|
|
272
|
+
for output formats, exit codes, readiness, and failure handling.
|
|
273
|
+
|
|
265
274
|
### `gc`
|
|
266
275
|
|
|
267
276
|
Enforce deadlines immediately. Persistent boxes stop; disposable boxes are deleted.
|
|
@@ -39,7 +39,7 @@ The configuration file maps location names to host definition objects:
|
|
|
39
39
|
### Location keys
|
|
40
40
|
|
|
41
41
|
- **Naming format**: Matches `^[a-zA-Z0-9][a-zA-Z0-9_-]*$`. Starts with an alphanumeric character and contains ASCII letters, digits, underscores, or hyphens.
|
|
42
|
-
- **Default location**: The first location listed is the default for `raft new`. `raft list` and `raft
|
|
42
|
+
- **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
43
|
|
|
44
44
|
### Host entry fields
|
|
45
45
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Raft 0.2.1
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Raft 0.2.1
|
|
6
|
+
|
|
7
|
+
- `raft doctor` now rejects an incompatible host expiry worker and identifies the location that needs deployment. It does not run expiry or change boxes.
|
|
8
|
+
- Lifecycle transactions close controller stdin so allocation cannot wait for piped input while holding the host lock.
|
|
9
|
+
|
|
10
|
+
Install on Linux with Python 3.11.8+ using `npm install -g @microck/raft@0.2.1`, or install the attached Python wheel. Updating the CLI does not update host workers. Deploy the matching checkout to hosts that fail the readiness check.
|
|
11
|
+
|
|
12
|
+
Exec, SSH, and file-transfer behavior is unchanged. Piped input to `raft exec` is unsupported.
|
|
13
|
+
|
|
14
|
+
ARM64 and AMD64 development images accompany this release. Verify `SHA256SUMS` before importing an image for your host architecture.
|
package/docs/verification.mdx
CHANGED
|
@@ -57,9 +57,15 @@ The [final runtime validation](https://github.com/Microck/raft/actions/runs/3754
|
|
|
57
57
|
|
|
58
58
|
## New workspace controls
|
|
59
59
|
|
|
60
|
-
`python3 deploy/verify-features.py --location lab` verifies recursive trees and unsafe-archive rejection, templates from a running source, snapshot deletion, CPU/RAM overrides, disposable timer deletion, and retention preview/apply. It uses only its own fixtures. Run the archive checks without `--location` to avoid allocating boxes.
|
|
60
|
+
`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, and retention preview/apply. It uses only its own fixtures. Run the archive checks without `--location` to avoid allocating boxes.
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
The live suite also holds a controller stdin pipe open while running real remote
|
|
63
|
+
readers through both captured and streamed lifecycle transactions. Both must
|
|
64
|
+
receive EOF without consuming controller input, and a subsequent transaction
|
|
65
|
+
must acquire the same host lock. This covers allocation stalls caused by Incus
|
|
66
|
+
waiting for stdin while the expiration worker waits for the lifecycle lock.
|
|
67
|
+
|
|
68
|
+
For an older deployment whose services should stay unchanged, `--isolated-worker` first verifies that `doctor` rejects the stale installed worker with a location-specific deployment diagnostic, then uses the current worker under a private temporary path. This checks worker logic without installing it; automatic timer integration is verified on disposable CI hosts. Current deployments use the normal verification mode; the isolated mode rejects them before allocation.
|
|
63
69
|
|
|
64
70
|
## Workspace controls validation (2026-10-07)
|
|
65
71
|
|
package/package.json
CHANGED
package/raft.py
CHANGED
|
@@ -16,7 +16,7 @@ import uuid
|
|
|
16
16
|
|
|
17
17
|
import raft_files
|
|
18
18
|
|
|
19
|
-
__version__ = "0.2.
|
|
19
|
+
__version__ = "0.2.1"
|
|
20
20
|
|
|
21
21
|
CONFIG = Path.home() / ".config/raft/incus.json"
|
|
22
22
|
PROJECT = "raft"
|
|
@@ -304,7 +304,9 @@ def transaction(location, script, *arguments, stdout=None):
|
|
|
304
304
|
*arguments,
|
|
305
305
|
]
|
|
306
306
|
connection = [
|
|
307
|
+
# Lifecycle transactions take arguments, never interactive or piped input.
|
|
307
308
|
"ssh",
|
|
309
|
+
"-n",
|
|
308
310
|
"-T",
|
|
309
311
|
"-o",
|
|
310
312
|
"BatchMode=yes",
|
|
@@ -710,6 +712,10 @@ def dispatch(args):
|
|
|
710
712
|
print(output, end="")
|
|
711
713
|
elif args.action == "doctor":
|
|
712
714
|
print(location + ": " + incus(location, "version").strip())
|
|
715
|
+
try:
|
|
716
|
+
transaction(location, CHECK_WORKER)
|
|
717
|
+
except RuntimeError as error:
|
|
718
|
+
raise RuntimeError(f"{location}: {error}") from error
|
|
713
719
|
response = subprocess.run(
|
|
714
720
|
[
|
|
715
721
|
"ssh",
|