@microck/raft 0.2.0 → 0.3.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/docs/contract.md CHANGED
@@ -23,6 +23,7 @@ Raft manages persistent unprivileged native-architecture Incus system containers
23
23
  - **Strict infrastructure validation**: Deployment refuses conflicting storage or network names and incomplete infrastructure rather than overwriting or repairing state.
24
24
  - **Storage pool sizing**: Existing owned Btrfs pools must retain the configured 60 GiB size; drift is rejected without automatic resizing.
25
25
  - **Firewall precedence**: Redeployment reapplies firewall rules. Incus startup requires successful firewall application before the daemon starts, including socket activation.
26
+ - **Guided onboarding**: `raft host add LOCATION --ssh TARGET` probes the target read-only and refuses unsupported hosts, including macOS, before any change. It prints the exact plan and applies it only after an interactive yes or `--yes`; `--dry-run` stops after the plan. It runs the same deployer as `deploy/deploy.py`, imports an image only after its SHA-256 matches (release `SHA256SUMS` or the local archive digest) and its architecture matches the host, then registers the location with the image fingerprint. It finishes with `doctor` and a disposable create/exec/destroy smoke test; if either fails, it removes the new location from the configuration and leaves host changes for a rerun. It uses existing key-based SSH and never collects passwords or changes SSH, other firewall rules or IP configuration. It requires the deployment files from the npm package or a source checkout.
26
27
  - **CLI coverage**: The single-operator workflow spans lifecycle, execution, files, snapshots, jobs, tunnels, backup, and capacity inspection; see the [command reference](./reference/cli.md).
27
28
 
28
29
  ## Configuration and lifecycle defaults
@@ -32,13 +33,15 @@ Raft manages persistent unprivileged native-architecture Incus system containers
32
33
  | Host identification | Configuration requires named SSH targets using letters, digits, underscores, or hyphens. |
33
34
  | Qualified handles | Every handle includes location and instance name (`<location>:<name>`). |
34
35
  | Image pinning | Workspace creation requires the immutable 64-hex image fingerprint, not an updating alias. |
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
+ | 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/8 GiB RAM, and 60 to 2,592,000 seconds (30 days) TTL. |
36
37
  | Admission limit | Hard ceiling of four saved boxes per host, including stopped boxes. |
37
38
  | 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. |
39
+ | 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
40
 
40
41
  ## Capacity and admission inspection
41
42
 
43
+ `raft locations` lists configured locations as JSONL with `location`, `default` and `note` fields without contacting hosts. `raft note LOCATION TEXT` and `raft note LOCATION --clear` edit the optional `note` field in the controller configuration atomically, preserving location order and mode `0600`. Notes are advisory guidance; Raft shows them in `locations`, `limits` and on stderr from `new`, and never enforces them.
44
+
42
45
  `raft limits [--location NAME] [--json]` inspects hosts without modifying state. It reports effective CPUs, total and available RAM, saved and active box counts, active resource limits, and disk space.
43
46
 
44
47
  - **Reserve calculations**: Recommends running boxes after reserving 1 CPU and the larger of 2 GiB or 10% host RAM.
@@ -51,7 +54,7 @@ Raft manages persistent unprivileged native-architecture Incus system containers
51
54
 
52
55
  - **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
56
  - **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**: Disposable creation/fork and retention cleanup require the current worker protocol. A mismatch fails before executing deletion policy or creating a disposable box; deploy the current checkout explicitly.
57
+ - **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
58
  - **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
59
  - **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
60
  - **Resizing**: Resume and fork accept optional supported CPU/RAM overrides, preserving omitted values. Resume validates stopped state under the lock before changing sizing.
@@ -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` starts `raft-desktop.service` inside the container if needed, waits for Xvfb and VNC readiness, and opens a tunnel to local port `6080`.
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.
@@ -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,134 @@
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
+ | `locations` | One JSON object per configured location (JSONL) |
27
+ | `prune` | JSONL candidates or deleted records with `box`, `action` |
28
+ | `status` | systemd `ActiveState`, `SubState`, `ExecMainStatus` property lines |
29
+ | `logs` | Guest journal text |
30
+ | `exec` | Payload stdout/stderr |
31
+ | Other inspection/maintenance commands | Human-readable text or no output |
32
+
33
+ `list` emits nothing when there are no boxes. `stopAt` is the host Unix deadline
34
+ as a string, or an empty string when absent. `info` uses native Incus keys rather
35
+ than a normalized Raft schema. `usage` includes `memoryBytes`, `memoryLimit`,
36
+ `cpuTime`, `effectiveCpus`, `filesystemUsedBytes`, `filesystemAvailableBytes`, and
37
+ `filesystemScope`; disk values describe the shared pool, not an individual box.
38
+ See [Capacity limits](../reference/capacity-limits.mdx) for capacity calculations.
39
+
40
+ These examples require `jq` on the controller:
41
+
42
+ ```sh title="CONTROLLER: Parse records without mixing stderr"
43
+ raft list --location lab | jq -r 'select(.state == "Running") | .box'
44
+ raft limits --location lab --json | jq '{location, savedSlots, warnings}'
45
+ raft prune --location lab --older-than 7 | jq -r '.box'
46
+ ```
47
+
48
+ Do not redirect `2>&1` into machine-readable output. Recovery reports its allocated
49
+ handle on stderr before import finishes; only a successful completion prints the
50
+ final handle on stdout.
51
+
52
+ ## Exit codes and job results
53
+
54
+ | Situation | Exit code |
55
+ | --- | --- |
56
+ | Successful control operation | `0` |
57
+ | Synchronous `exec` | Transport/command return code; preserves payload exit status when transport succeeds |
58
+ | Recognized runtime/configuration failure | `1`, with `raft: ...` on stderr |
59
+ | Argument parsing failure | `2`, with argparse diagnostics on stderr |
60
+ | Caught keyboard interruption | `130` |
61
+
62
+ SSH transport failures may return SSH's status rather than the payload's result.
63
+ The foreground tunnel replaces the CLI process with SSH and uses SSH's exit code.
64
+ A zero exit from `status` means the query succeeded; read `ExecMainStatus` for the
65
+ job result and `ActiveState`/`SubState` for whether it finished. Do not interpret a
66
+ running job's initial `ExecMainStatus=0` as completion. See
67
+ [Background jobs](./background-jobs.mdx).
68
+
69
+ ## Create, wait, run, and stop
70
+
71
+ `new` returns after Incus starts the box, before every guest service is necessarily
72
+ ready. Probe a harmless command before running setup. This Bash example waits up
73
+ to about a minute plus transport time, executes an uploaded project check, and
74
+ attempts to stop the persistent box on exit. It assumes `./my-project/check.sh`
75
+ exists and the configured host has a free saved slot:
76
+
77
+ ```bash title="CONTROLLER or trusted Linux CI runner"
78
+ set -euo pipefail
79
+ box=""
80
+ cleanup() {
81
+ result=$?
82
+ trap - EXIT
83
+ if [ -n "$box" ]; then
84
+ if ! raft stop "$box"; then
85
+ echo "Cleanup failed; inspect $box" >&2
86
+ [ "$result" -ne 0 ] || result=1
87
+ fi
88
+ fi
89
+ exit "$result"
90
+ }
91
+ trap cleanup EXIT
92
+
93
+ box=$(raft new --location lab --ttl 3600)
94
+ printf 'Created %s\n' "$box" >&2
95
+ ready=false
96
+ for attempt in {1..30}; do
97
+ if raft exec "$box" -- true; then
98
+ ready=true
99
+ break
100
+ fi
101
+ sleep 2
102
+ done
103
+ if [ "$ready" != true ]; then
104
+ echo "Guest did not become command-ready: $box" >&2
105
+ exit 1
106
+ fi
107
+
108
+ raft upload "$box" ./my-project /workspace/my-project --recursive
109
+ raft exec "$box" -- bash -lc 'cd /workspace/my-project && bash ./check.sh'
110
+ ```
111
+
112
+ The stopped box remains available for inspection and still consumes a saved slot.
113
+ Destroy it after collecting needed files, or preview old stopped boxes with prune.
114
+ For disposable CI boxes, deletion on stop/expiry removes artifacts and snapshots;
115
+ download results before cleanup. A new box or a retry still consumes a saved slot.
116
+
117
+ Command readiness does not mean Docker or a web application is ready. Probe the
118
+ specific dependency (`docker info`, an HTTP health endpoint, or systemd status)
119
+ with its own bounded wait. Extend the deadline before work exceeding the current
120
+ lifetime. This example's retries are only for the harmless readiness probe.
121
+
122
+ ## Interrupted operations and retries
123
+
124
+ Raft does not automatically retry commands or provide idempotency keys. If SSH
125
+ drops during creation, resume, import, or execution, the remote operation may have
126
+ already happened. Inspect `raft list`, `raft info`, guest logs, and the host before
127
+ repeating an operation that creates boxes or changes data. A failed create can
128
+ leave a saved box even when no handle reached stdout.
129
+
130
+ For an interrupted recovery, inspect the handle printed on stderr and follow
131
+ [Host crash during recovery](./troubleshooting.mdx#host-crash-during-recovery).
132
+ For uncertain command execution, check its effects before rerunning it; use
133
+ idempotent setup scripts where possible. Do not treat a lost connection as proof
134
+ 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 stops expired containers every 15 seconds. Trigger an immediate expiration run from the controller:
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`. Expiration never deletes container files or snapshots.
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) |