@microck/raft 0.4.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 +30 -14
- package/deploy/deploy.py +112 -19
- package/deploy/install-incus.sh +10 -0
- package/deploy/raft-expire.py +109 -31
- package/deploy/verify-controls.py +34 -48
- package/deploy/verify-features.py +592 -3
- package/deploy/verify-fresh-host.py +64 -1
- package/deploy/verify-limits.py +327 -72
- package/deploy/verify-live.py +56 -31
- package/deploy/verify-self-update.py +372 -0
- package/docs/boat-capabilities.md +8 -7
- package/docs/boot-performance.mdx +1 -1
- package/docs/contract.md +33 -23
- package/docs/first-box.mdx +1 -1
- package/docs/guides/backup-recovery.mdx +2 -2
- package/docs/guides/config-policy.mdx +253 -0
- package/docs/guides/docker.mdx +9 -1
- package/docs/guides/long-running-services.mdx +10 -4
- package/docs/guides/meta.json +1 -0
- package/docs/guides/project-setup.mdx +4 -3
- package/docs/guides/scripting-ci.mdx +27 -10
- package/docs/guides/snapshots-forks.mdx +9 -3
- package/docs/guides/troubleshooting.mdx +37 -8
- package/docs/guides/upgrades.mdx +17 -5
- package/docs/index.mdx +1 -0
- package/docs/operations.mdx +16 -14
- package/docs/reference/capacity.mdx +198 -0
- package/docs/reference/cli.md +178 -35
- package/docs/reference/configuration.mdx +40 -2
- package/docs/reference/meta.json +1 -1
- package/docs/releases/0.5.0.md +19 -0
- package/docs/releases/0.6.0.md +16 -0
- package/docs/requirements.mdx +2 -2
- package/docs/verification.mdx +14 -8
- package/package.json +1 -1
- package/raft.py +1181 -128
- package/skills/raft-cli/SKILL.md +79 -13
- package/docs/reference/capacity-limits.mdx +0 -92
package/docs/contract.md
CHANGED
|
@@ -17,14 +17,16 @@ Raft manages persistent unprivileged native-architecture Incus system containers
|
|
|
17
17
|
- npm publishes `@microck/raft` with the same Python sources as the wheel and source archive. Its executable is `raft`; Linux and Python 3.11.8+ are required. Installation runs no scripts and downloads no workspace images.
|
|
18
18
|
- `raft --version` reports the release version without reading host configuration. The Python package, npm metadata and release tag must agree before publication.
|
|
19
19
|
- Releases include Python wheels, source archives, npm tarballs and both native images with checksums. The release workflow verifies installation outside the checkout and publishes npm with provenance before creating the GitHub release.
|
|
20
|
+
- **Self-update**: `raft self-update [--check | --version X.Y.Z]` takes the latest version from the GitHub releases API and compares versions numerically. It upgrades only npm global installs (through the registry, at the same prefix) and uv tools (a release wheel verified against the release `SHA256SUMS`), and refuses checkouts and other layouts. It never downgrades without `--version`, never changes hosts, and prints `raft doctor` as the next step. `--check` exits `100` when a newer release exists.
|
|
21
|
+
- **Release notice**: At most once per 24 hours a command checks the latest release in the background and, if it is newer, prints one stderr line after the command. It never installs, prompts, changes stdout or exit status, or waits more than 2 seconds; failures stay silent. `RAFT_NO_UPDATE_CHECK=1` disables it, and `self-update` and `--version` skip it.
|
|
20
22
|
|
|
21
23
|
## Infrastructure and deployment
|
|
22
24
|
|
|
23
25
|
- **Strict infrastructure validation**: Deployment refuses conflicting storage or network names and incomplete infrastructure rather than overwriting or repairing state.
|
|
24
|
-
- **Storage pool sizing**:
|
|
26
|
+
- **Storage pool sizing**: The shared Btrfs pool `raft-data` must retain its configured 60 GiB size. The own-disk pool `raft-disks` is opt-in: deployment creates it only when `--disk-pool SIZE` is given, on a fresh host or on any later redeploy, as a loop-backed LVM thin pool (`lvm.use_thinpool=true`) of SIZE whose file is allocated in full on the host disk and whose thin pool uses `--discards nopassdown`. A fresh host needs 80 GiB free, plus SIZE when `--disk-pool` is given; adding the pool later needs SIZE plus 20 GiB free. A host without `raft-disks` is healthy. An existing `raft-disks` keeps its size, a different explicit `--disk-pool` is refused, and a pool with another driver or thick volumes is rejected. Deployment never resizes either pool.
|
|
25
27
|
- **Firewall precedence**: Redeployment reapplies firewall rules. Incus startup requires successful firewall application before the daemon starts, including socket activation.
|
|
26
28
|
- **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.
|
|
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).
|
|
29
|
+
- **CLI coverage**: The single-operator workflow spans lifecycle, execution, files, snapshots, jobs, tunnels, backup, and advisory capacity inspection; see the [command reference](./reference/cli.md).
|
|
28
30
|
|
|
29
31
|
## Configuration and lifecycle defaults
|
|
30
32
|
|
|
@@ -33,38 +35,45 @@ Raft manages persistent unprivileged native-architecture Incus system containers
|
|
|
33
35
|
| Host identification | Configuration requires named SSH targets using letters, digits, underscores, or hyphens. |
|
|
34
36
|
| Qualified handles | Every handle includes location and instance name (`<location>:<name>`). |
|
|
35
37
|
| Image pinning | Workspace creation requires the immutable 64-hex image fingerprint, not an updating alias. |
|
|
36
|
-
| Lifecycle defaults |
|
|
37
|
-
|
|
|
38
|
+
| Lifecycle defaults | Built-in defaults are the first configured location, 1 CPU, 2 GiB RAM, the shared pool, a persistent box and 600-second lifetime. `~/.config/raft/config.json` can override them for `new` only; explicit flags win, and `--persistent` overrides a `disposable` default. Boxes from a snapshot (`new --from`) and forks keep the source's CPU, RAM and disk instead of any default, configured or built-in; each flag overrides only its own value. `--cpu` takes any positive whole number, `--memory` any positive whole GiB (`16GiB`), `--disk` a positive whole GiB or `shared`, and TTL 60 to 2,592,000 seconds (30 days). |
|
|
39
|
+
| Box count and size | Raft limits neither how many boxes a host holds nor their size. CPU or RAM limits larger than the whole host (CPU count, or `MemTotal` rounded up to whole GiB) are refused under the host lock before any change, because Incus would clip them silently. Other failures come from Incus, such as too little pool space. |
|
|
38
40
|
| TTL calculation | Running TTL begins after launch completes, using the host clock. Cold image preparation does not consume TTL. |
|
|
39
41
|
| 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. |
|
|
40
42
|
|
|
41
|
-
##
|
|
43
|
+
## Locations, notes and capacity inspection
|
|
42
44
|
|
|
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`, `
|
|
45
|
+
`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`, `capacity` and on stderr from `new`, and never enforces them.
|
|
44
46
|
|
|
45
|
-
Boxes accept an optional note with a fixed template: `purpose` (required), `owner` and `next`, all nonempty strings. `raft new --purpose TEXT [--owner TEXT] [--next TEXT]` and `raft note BOX ...` store it as JSON in the box's `user.raft.note` Incus key; `raft note BOX --clear` removes it. Each edit replaces the whole note. `raft list` reports it as `note`, or `null`. `new` always writes the key, so `new --from` never inherits a snapshot's note
|
|
47
|
+
Boxes accept an optional note with a fixed template: `purpose` (required), `owner` and `next`, all nonempty strings. `raft new --purpose TEXT [--owner TEXT] [--next TEXT]` and `raft note BOX ...` store it as JSON in the box's `user.raft.note` Incus key; `raft note BOX --clear` removes it. Each edit replaces the whole note. `raft list` reports it as `note`, or `null`, reports each box's `disk` as `"shared"` or its own disk size, and reports `deleteAt`, the Unix time retention deletes a stopped box, or `""`. `new` always writes the key, so `new --from` never inherits a snapshot's note. Forks and recovered backups keep it unless `fork` or `recover` gets note flags, which replace the whole note. Box notes never change lifecycle behavior.
|
|
46
48
|
|
|
47
|
-
|
|
49
|
+
## Controller defaults and host policies
|
|
48
50
|
|
|
49
|
-
- **
|
|
50
|
-
- **
|
|
51
|
-
- **
|
|
52
|
-
- **
|
|
53
|
-
- **
|
|
51
|
+
- **Controller defaults**: `~/.config/raft/config.json` holds `{"defaults": {...}}` with optional `location`, `ttl`, `cpu`, `memory`, `disk` and `disposable` for `new`. Precedence is explicit flag, then configured default, then built-in default. `cpu`, `memory` and `disk` apply only to boxes from the image: `new --from` keeps the snapshot's location, CPU, RAM and disk unless a flag overrides them, and `--disk shared` is refused there. `ttl` and `disposable` apply to `new --from` as well, because a snapshot carries no lifecycle; `new --disposable` and `--persistent` are mutually exclusive overrides. `fork` takes no defaults and is persistent without `--disposable`. A `disk` default on a host without `raft-disks`, or larger than its unreserved space, fails like `--disk` and names `defaults.disk` as the source. Other commands take no defaults. A value is valid exactly when the same text is valid for the flag. Unknown keys, bad values and malformed JSON fail `new` with the file path and key, before any host access. `raft config set/unset` validate first, write atomically under a controller lock, keep mode `0600`, print the change and can repair the key they edit. The file is separate from `incus.json`, so location names never collide with setting names.
|
|
52
|
+
- **Policy storage**: `raft policy LOCATION [--require FIELDS|none] [--delete-stopped-after HOURS|never]` sets either host policy or both; each option changes only its own. Required box note fields, which must include `purpose`, live in the Incus project key `user.raft.require`, and `--require none` empties it. Stopped-box retention lives in `user.raft.delete-stopped-after` as a positive whole number of hours, with the time it was set in `user.raft.delete-stopped-after.since`; `never` empties both. Both values are validated before any host change. Each change runs under the host lock, prints the old and new value, records UTC time, the SSH account (`SUDO_USER`) and the self-reported controller user and hostname in the policy's `.changed` key, and logs to the host journal with tag `raft`. Redeployment leaves these keys unchanged.
|
|
53
|
+
- **Note policy enforcement**: `new` (including `--from`), `fork`, `recover` and `raft note BOX` check the note the box will carry. The controller reads the policy first and fails with the missing flags and a complete corrected command before changing anything. The host repeats the check inside the transaction that writes the note, under the host lock, so a concurrent policy change cannot be missed. That transaction also holds the oversize and own-disk reservation checks; all of them run before Incus creates anything. `fork` checks the given note or, without one, the source note read under the lock. `recover` checks a given note before upload; an archived note is checked after import, and a failing import is deleted in the same transaction. `note --clear` and replacement notes that drop a required field are rejected.
|
|
54
|
+
- **Retention enforcement**: The host expiry worker deletes a stopped Raft box, with its snapshots, once the hours have passed since the later of its `user.raft.stopped-at` and the policy's `.since` time, so setting, shortening or renewing a policy never deletes a box sooner than its hours after the change. It runs on the 15-second timer and on `raft gc`, under the host lock, and writes each deletion to the host journal with tag `raft`. Running boxes are never touched. A stopped Raft box without `user.raft.stopped-at`, from a guest poweroff, a native stop, a failed start or `raft recover`, gets the worker's current time as its stop time, never an earlier guess, whether or not a policy is set. A malformed retention value makes the worker delete nothing by retention, still enforce deadlines, and exit nonzero; the CLI reports it with the fix. Setting retention requires the current worker protocol; clearing it does not.
|
|
55
|
+
- **Policy scope**: Policies are guardrails for agents, not access controls. Anyone with host SSH and sudo can change them or create boxes with native Incus commands, and controllers without note policy support do not check it. A native `incus start` leaves a stale stop time that retention trusts. `raft config` reports every location's policies and last changes.
|
|
56
|
+
|
|
57
|
+
`raft capacity [--location NAME] [--json]` inspects hosts without modifying state. Text and JSON (`notice`) lead with: "Advisory estimate only. Raft enforces no box count or size budget; create the boxes your task needs." It reports effective CPUs, total and available RAM, saved and active box counts, active resource limits, headroom, free space in the host disk and the shared pool, and for `raft-disks` its data space, reserved and used bytes and metadata use (`boxDiskPool`, `null` when own box disks are not enabled).
|
|
58
|
+
|
|
59
|
+
- **Reserve calculations**: Estimates leave out 1 CPU and the larger of 2 GiB or 10% host RAM.
|
|
60
|
+
- **Example shapes**: `estimates` lists shapes doubling from 1 CPU / 2 GiB at 2 GiB per CPU while they fit an empty host, with `fitEmptyHost` and `fitNow` counts. No count is capped.
|
|
61
|
+
- **Headroom**: Subtracts active configured limits and keeps available RAM above the reserve. Every non-`Stopped` state (including `Frozen`) counts as active.
|
|
62
|
+
- **Advisory only**: Nothing in the report blocks creation, resume or fork. Disk space below 5 GiB and `raft-disks` thin pool metadata above 80% trigger warnings; a missing `raft-disks` does not.
|
|
54
63
|
|
|
55
64
|
## Container lifecycle and execution
|
|
56
65
|
|
|
57
66
|
- **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.
|
|
58
67
|
- **Lifetime extension**: `raft extend --ttl` resets a running box's deadline from current host time without restarting; stopped boxes require `raft resume`.
|
|
59
|
-
- **Host worker contract**: `doctor`, disposable creation/fork, and retention
|
|
60
|
-
- **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`.
|
|
61
|
-
- **
|
|
62
|
-
- **Resizing**: Resume and fork accept optional
|
|
63
|
-
- **Expiration cleanup**: The host timer checks expirations every 15 seconds, stopping expired persistent containers
|
|
64
|
-
- **Destroy**: Removes the container instance and its snapshots, releasing storage. Destroy confirms absence; SSH failure is not proof of deletion. Disposable boxes created with `new --disposable` are deleted on explicit stop or expiry. Persistent boxes still require explicit destroy for deletion.
|
|
68
|
+
- **Host worker contract**: `doctor`, disposable creation/fork, prune and setting a retention policy require the current worker protocol (2 since 0.6.0, which added retention). `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.
|
|
69
|
+
- **Disposable lifecycle**: `new --disposable`, or a configured `disposable` default without `--persistent`, opts a box into deletion on stop or TTL expiry, including its snapshots. Clones and recovered boxes default to persistent whatever the default; fork accepts an explicit `--disposable`.
|
|
70
|
+
- **Manual 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. Automatic deletion of persistent stopped boxes happens only under a host retention policy.
|
|
71
|
+
- **Resizing**: Resume and fork accept optional CPU/RAM overrides, preserving omitted values. Resume validates stopped state under the lock before changing sizing. Neither changes a box's disk.
|
|
72
|
+
- **Expiration cleanup**: The host timer checks expirations every 15 seconds, stopping expired persistent containers, deleting expired disposable containers and applying stopped-box retention. Expiry may be delayed by graceful shutdown, long locked transactions, and host outage until startup.
|
|
73
|
+
- **Destroy**: Removes the container instance and its snapshots, releasing storage. Destroy confirms absence; SSH failure is not proof of deletion. Disposable boxes created with `new --disposable` are deleted on explicit stop or expiry. Persistent boxes still require explicit destroy, prune or a retention policy for deletion.
|
|
65
74
|
- **Execution transport**: Incus exec and file APIs travel over authenticated host SSH without a public Incus listener or injected credentials. Interactive SSH runs a PTY over host SSH and Incus exec.
|
|
66
75
|
- **State inspection and tunnels**: `raft list` and `raft info` read native metadata without guest process counters during shutdown; tunnels use the managed NIC.
|
|
67
|
-
- **Resource reporting**: `raft usage` reports running cgroup memory, CPU microseconds, CPU affinity, and shared pool
|
|
76
|
+
- **Resource reporting**: `raft usage` reports running cgroup memory, CPU microseconds, CPU affinity, and filesystem space with `filesystemScope`: the shared pool, or `own SIZE box disk`. It provides no billing.
|
|
68
77
|
- **Recursive transfers**: `upload/download --recursive` require a running workspace and Python 3.11.8+ in the guest. They stream a tar archive and publish a new destination directory atomically without overwriting or merging. Relative links contained within the tree, regular files, directories and internal hardlinks are supported; escaping paths/links and special files are rejected. Source entries are pinned with no-follow descriptors before metadata inspection, file reads or directory descent, so pathname replacement cannot redirect access outside the selected tree. Failed transfers leave existing destinations unchanged.
|
|
69
78
|
- **Directory transfer completion**: The receiver verifies the sender's exact archive byte count before publication. A truncated stream, including one ending between complete entries, is rejected without publishing a partial tree.
|
|
70
79
|
- **File transfer failures**: Controller write errors return an error before replacing a download destination or publishing a backup archive.
|
|
@@ -79,10 +88,11 @@ Boxes accept an optional note with a fixed template: `purpose` (required), `owne
|
|
|
79
88
|
|
|
80
89
|
## Storage, snapshots, and cloning
|
|
81
90
|
|
|
82
|
-
- **
|
|
91
|
+
- **Shared pool**: Boxes default to a dedicated 60 GiB Btrfs pool per host with instant snapshots and clones. Btrfs quotas cannot bound a box there, because nested subvolumes (Docker's, for example) escape them. The native compressed image cache resides outside both pools, so host disk space still matters.
|
|
92
|
+
- **Own box disks**: `new --disk SIZE` puts the box's root on an LVM thin volume of SIZE in `raft-disks`, formatted ext4. All guest writes, including Docker data, land in that filesystem, so writes fail with ENOSPC at SIZE. New own-disk boxes are thin snapshots of the cached image volume, and forks, templates and snapshots of own-disk boxes are thin snapshots of their source. The pool is never overcommitted: under the host lock, before `new`, `new --from`, `fork`, `snapshot` or `recover` adds thin volumes to `raft-disks`, Raft sums the sizes of every thin volume in the pool (from `lvs`) plus the new ones and refuses, before creating anything, if the total exceeds the thin pool's data space. A new volume counts as the larger of its size and its source's; the first own-disk box on a host also counts the image volume (`volume.size`, or 10 GiB); a recovered backup counts its size once per snapshot plus once. `restore` reserves nothing. On a host without `raft-disks`, `new --disk` and recovery of an own-disk backup fail before creating anything and name the deploy command that enables the pool. `fork` and `new --from` keep the source disk; `new --from ... --disk SIZE` copies onto an own disk of SIZE. A disk too small for its contents fails creation.
|
|
83
93
|
- **Stopped-state requirement**: Snapshots, restores and forks require a stopped source for consistent disk contents. Operations run under the host lifecycle lock.
|
|
84
94
|
- **Snapshot restore**: `raft restore` rolls back disk blocks via `--diskonly`, retaining container configuration, limits, and MAC address.
|
|
85
|
-
- **Snapshot management**: `snapshot-delete <box> <name>` removes one named snapshot under the lock. `new --from LOCATION:BOX/SNAPSHOT` creates a same-host box from that snapshot, with
|
|
95
|
+
- **Snapshot management**: `snapshot-delete <box> <name>` removes one named snapshot under the lock. `new --from LOCATION:BOX/SNAPSHOT` creates a same-host box from that snapshot, with the snapshot's CPU, RAM and disk unless `--cpu`, `--memory` or `--disk` overrides them, and a fresh network identity. The source may run; the named snapshot is immutable. Templates retain filesystem secrets.
|
|
86
96
|
- **Workspace forks**: `raft fork` copies filesystem data and secrets to a new container with an independent Incus name, fresh MAC address, and renewed TTL. Source snapshots are not inherited.
|
|
87
97
|
|
|
88
98
|
## Background jobs and desktop
|
|
@@ -94,5 +104,5 @@ Boxes accept an optional note with a fixed template: `purpose` (required), `owne
|
|
|
94
104
|
|
|
95
105
|
- **Export safeguards**: `raft backup` exports stopped containers and snapshots to a local archive. Destination path must not exist, requires mode `0600`, and uses atomic hard-link publication.
|
|
96
106
|
- **Host locking**: Export holds the host lifecycle lock, temporarily delaying expiry checks on the source host.
|
|
97
|
-
- **Recovery
|
|
107
|
+
- **Recovery**: `raft recover` imports archives into a stopped container with a fresh name, new MAC address, and cleared TTL. It reads the archive's recorded pool from `backup/index.yaml`: own-disk backups return to `raft-disks` with their size after the reservation check, failing on a host without the pool, and other archives land in `raft-data`. The host note policy applies after the reservation check. Recovery never overwrites existing boxes.
|
|
98
108
|
- **Archive security**: Archives contain full container filesystems, including credentials. Native archive configuration is not sanitized; backups do not imply scheduled off-host uploads. Store archives privately and import only trusted exports.
|
package/docs/first-box.mdx
CHANGED
|
@@ -83,4 +83,4 @@ raft destroy "$box"
|
|
|
83
83
|
- [Troubleshooting](./guides/troubleshooting.mdx): Diagnose common errors and inspect host services.
|
|
84
84
|
- [Upgrades and image changes](./guides/upgrades.mdx): Update the controller, hosts, and image pins.
|
|
85
85
|
|
|
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.
|
|
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. A host can also delete boxes left stopped automatically; `raft config` shows each location's `deleteStoppedAfterHours`.
|
|
@@ -53,8 +53,8 @@ echo "Recovered workspace: $recovered"
|
|
|
53
53
|
|
|
54
54
|
- **Architecture compatibility**: Target host must match the native CPU architecture (ARM64 to ARM64, AMD64 to AMD64). Cross-architecture recovery is not supported.
|
|
55
55
|
- **Fresh network identity**: The container receives a new name in project `raft`, a new MAC address on `rfbr0`, and no inherited TTL deadline.
|
|
56
|
-
- **Stopped initial state**: Recovered workspaces remain stopped until started with `raft resume "$recovered" --ttl <SECONDS>`.
|
|
57
|
-
- **
|
|
56
|
+
- **Stopped initial state**: Recovered workspaces remain stopped until started with `raft resume "$recovered" --ttl <SECONDS>`. Under a host [retention policy](./config-policy.mdx#delete-boxes-left-stopped), the recovered box's clock starts when the host worker first sees it, within about 15 seconds; resume it before the policy's hours pass.
|
|
57
|
+
- **Disk placement**: A backup of a box with its own disk returns to an own disk of the same size in `raft-disks`. The destination needs own box disks enabled and enough unreserved pool space for the box and each of its snapshots; otherwise recovery fails before importing. Other backups land in the shared pool.
|
|
58
58
|
- **Interrupted recovery**: Follow [Host crash during recovery](./troubleshooting.mdx#host-crash-during-recovery). Inspect the reported handle and container state before deleting any staging file.
|
|
59
59
|
|
|
60
60
|
Start and verify the recovered workspace:
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
---
|
|
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
|
+
---
|
|
5
|
+
|
|
6
|
+
# Defaults and host policies
|
|
7
|
+
|
|
8
|
+
Raft has two kinds of settings:
|
|
9
|
+
|
|
10
|
+
| Setting | Stored in | Applies to | Change with |
|
|
11
|
+
| --- | --- | --- | --- |
|
|
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 --require` |
|
|
14
|
+
| Stopped-box retention | Incus project `raft` on each host | Every box on the host | `raft policy --delete-stopped-after` |
|
|
15
|
+
|
|
16
|
+
`raft config` prints both kinds: the effective defaults with their source, and
|
|
17
|
+
each location's policies.
|
|
18
|
+
|
|
19
|
+
```sh title="CONTROLLER: Inspect defaults and policies"
|
|
20
|
+
raft config
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```json title="Example output"
|
|
24
|
+
{
|
|
25
|
+
"file": "/root/.config/raft/config.json",
|
|
26
|
+
"defaults": {
|
|
27
|
+
"location": {
|
|
28
|
+
"value": "lab",
|
|
29
|
+
"source": "config"
|
|
30
|
+
},
|
|
31
|
+
"ttl": {
|
|
32
|
+
"value": 3600,
|
|
33
|
+
"source": "config"
|
|
34
|
+
},
|
|
35
|
+
"cpu": {
|
|
36
|
+
"value": 1,
|
|
37
|
+
"source": "built-in"
|
|
38
|
+
},
|
|
39
|
+
"memory": {
|
|
40
|
+
"value": "2GiB",
|
|
41
|
+
"source": "built-in"
|
|
42
|
+
},
|
|
43
|
+
"disk": {
|
|
44
|
+
"value": "shared",
|
|
45
|
+
"source": "built-in"
|
|
46
|
+
},
|
|
47
|
+
"disposable": {
|
|
48
|
+
"value": true,
|
|
49
|
+
"source": "config"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"policy": {
|
|
53
|
+
"lab": {
|
|
54
|
+
"require": [
|
|
55
|
+
"purpose",
|
|
56
|
+
"owner"
|
|
57
|
+
],
|
|
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"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`raft config` contacts every configured host to read its policy. It fails and
|
|
68
|
+
names the location when a host is unreachable.
|
|
69
|
+
|
|
70
|
+
## Defaults for new
|
|
71
|
+
|
|
72
|
+
Set a default for any of `location`, `ttl`, `cpu`, `memory`, `disk` and
|
|
73
|
+
`disposable`:
|
|
74
|
+
|
|
75
|
+
```sh title="CONTROLLER: Prefer bigger, longer, temporary boxes on paris"
|
|
76
|
+
raft config set location paris
|
|
77
|
+
raft config set ttl 3600
|
|
78
|
+
raft config set cpu 4
|
|
79
|
+
raft config set memory 16GiB
|
|
80
|
+
raft config set disk 20GiB
|
|
81
|
+
raft config set disposable true
|
|
82
|
+
raft config unset cpu
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Each command prints the old and new value. `raft new` takes an explicit flag
|
|
86
|
+
first, then the configured default, then the built-in default (first configured
|
|
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.
|
|
97
|
+
|
|
98
|
+
Size defaults (`cpu`, `memory`, `disk`) apply only where the built-in sizes do:
|
|
99
|
+
to boxes created from the image. A box from `new --from` keeps the snapshot's
|
|
100
|
+
location, CPU, RAM and disk, and a fork keeps its source's, whatever
|
|
101
|
+
`config.json` says. Only a flag changes them:
|
|
102
|
+
|
|
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 |
|
|
108
|
+
|
|
109
|
+
A `disk` default needs own box disks on the target host. On a location without
|
|
110
|
+
the `raft-disks` pool, or with too little unreserved space, `raft new` fails with
|
|
111
|
+
the same error as `--disk` and names the configured value as the cause:
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
$ raft new --purpose "Run the tests"
|
|
115
|
+
raft: lab has no raft-disks pool, so own box disks are not enabled there; nothing was created. Enable them with python3 deploy/deploy.py --location lab --disk-pool SIZE. The 20GiB own disk comes from defaults.disk in /root/.config/raft/config.json; give --disk shared or --disk SIZE, or run raft config unset disk
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`--disk shared` puts one box on the shared pool despite a `disk` default. It is
|
|
119
|
+
refused with `--from`, which keeps the snapshot's disk.
|
|
120
|
+
|
|
121
|
+
The file accepts a value exactly when the same text is accepted as the flag.
|
|
122
|
+
An unknown key, a bad value or malformed JSON makes `raft new` fail with the file
|
|
123
|
+
path and the key; Raft never ignores part of the file. Repair a bad key with
|
|
124
|
+
`raft config set KEY VALUE` or `raft config unset KEY`. Writes are atomic, keep
|
|
125
|
+
mode `0600` and remove the file when no default remains.
|
|
126
|
+
|
|
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.
|
|
130
|
+
|
|
131
|
+
## Required box note fields
|
|
132
|
+
|
|
133
|
+
A host policy makes every box on that host carry the listed [box note](../reference/cli.md#note)
|
|
134
|
+
fields. `purpose` is always part of a note, so a policy must include it:
|
|
135
|
+
|
|
136
|
+
```sh title="CONTROLLER: Require an owner on lab"
|
|
137
|
+
raft policy lab --require purpose,owner
|
|
138
|
+
raft policy lab --require none
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
With that policy, a command that would leave a box without an owner fails before
|
|
142
|
+
it changes anything and says how to fix it:
|
|
143
|
+
|
|
144
|
+
```text
|
|
145
|
+
$ raft new --location lab --purpose "Run the tests"
|
|
146
|
+
raft: location lab requires --owner (host policy). Add it: raft new --purpose "..." --owner "..." [--next "..."]
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The policy covers every command that creates a box or changes its note:
|
|
150
|
+
|
|
151
|
+
| Command | Note checked |
|
|
152
|
+
| --- | --- |
|
|
153
|
+
| `new`, including `new --from` | The note given to `new`; a snapshot's note is never inherited |
|
|
154
|
+
| `fork` | The note given to `fork`, otherwise the source box's note |
|
|
155
|
+
| `recover` | The note given to `recover`, otherwise the note in the archive |
|
|
156
|
+
| `note BOX` | The new note; `--clear` is rejected while a policy requires fields |
|
|
157
|
+
|
|
158
|
+
On `fork` and `recover`, `--purpose`, `--owner` and `--next` replace the whole
|
|
159
|
+
note, like `raft note`. Give all the fields the copy needs; omitted fields are
|
|
160
|
+
not taken from the source. `recover` without note flags can only check the
|
|
161
|
+
archived note after import. When it fails, Raft deletes the imported box in the
|
|
162
|
+
same locked step and reports the error. Pass the note to `recover` to fail before
|
|
163
|
+
the upload instead.
|
|
164
|
+
|
|
165
|
+
The controller checks the policy first, so the error arrives before any work.
|
|
166
|
+
The host checks it again in the same locked transaction that writes the note,
|
|
167
|
+
so a policy change cannot race a creation. That transaction checks the
|
|
168
|
+
requested size against the host and reserves own-disk space first; any refusal
|
|
169
|
+
comes before Incus creates the box.
|
|
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
|
+
|
|
219
|
+
## Audit trail
|
|
220
|
+
|
|
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:
|
|
228
|
+
|
|
229
|
+
```sh title="HOST: Review policy changes and retention deletions"
|
|
230
|
+
sudo incus project get raft user.raft.require
|
|
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
|
|
234
|
+
sudo journalctl -t raft
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Limits
|
|
238
|
+
|
|
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`.
|
|
252
|
+
- Raft checks that required fields are present and nonempty, not that their text
|
|
253
|
+
is accurate.
|
package/docs/guides/docker.mdx
CHANGED
|
@@ -64,7 +64,15 @@ docker run --rm my-app
|
|
|
64
64
|
|
|
65
65
|
## Managing disk space
|
|
66
66
|
|
|
67
|
-
Nested container layers and volumes
|
|
67
|
+
Nested container layers and volumes live in the box's filesystem. By default that is the shared 60 GiB `raft-data` Btrfs pool, where one box's Docker data can fill the pool for every box.
|
|
68
|
+
|
|
69
|
+
On hosts with own box disks enabled, give Docker-heavy boxes their own disk. Docker data stays inside it, and builds fail with "No space left on device" at the cap instead of filling the host:
|
|
70
|
+
|
|
71
|
+
```sh title="CONTROLLER: Create a box with its own 40 GiB disk"
|
|
72
|
+
box=$(raft new --disk 40GiB --ttl 3600 --purpose "Docker image builds")
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
See [Own box disks](../reference/capacity.mdx#own-box-disks).
|
|
68
76
|
|
|
69
77
|
Prune dangling images without deleting active container data:
|
|
70
78
|
|
|
@@ -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
|
-
Persistent stopped boxes still occupy
|
|
32
|
-
|
|
33
|
-
`--yes` applies deletion.
|
|
33
|
+
Persistent stopped boxes still occupy storage. Raft does not cap how many boxes
|
|
34
|
+
a host keeps. `raft prune --older-than 7` previews old stopped boxes;
|
|
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
|
|
package/docs/guides/meta.json
CHANGED
|
@@ -94,9 +94,10 @@ raft exec "$copy" -- ls -la /workspace/my-project
|
|
|
94
94
|
```
|
|
95
95
|
|
|
96
96
|
The template stays on its source host. Its source box can be running when you
|
|
97
|
-
copy the saved snapshot. `new --from`
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
copy the saved snapshot. `new --from` keeps the snapshot's CPU, RAM and disk
|
|
98
|
+
unless `--cpu`, `--memory` or `--disk SIZE` overrides them; `--disk` gives it an
|
|
99
|
+
own disk of a new size. The new box has its own handle, MAC address
|
|
100
|
+
and deadline.
|
|
100
101
|
|
|
101
102
|
The snapshot includes packages, project files, credentials, and enabled services.
|
|
102
103
|
It does not include process memory. Changing the original after snapshot creation
|
|
@@ -19,11 +19,13 @@ 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`, `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) |
|
|
26
26
|
| `locations` | One JSON object per configured location (JSONL) |
|
|
27
|
+
| `config` | One pretty-printed JSON object with `file`, `defaults`, `policy` |
|
|
28
|
+
| `config set/unset`, `policy` | One line describing the change |
|
|
27
29
|
| `prune` | JSONL candidates or deleted records with `box`, `action` |
|
|
28
30
|
| `status` | systemd `ActiveState`, `SubState`, `ExecMainStatus` property lines |
|
|
29
31
|
| `logs` | Guest journal text |
|
|
@@ -31,24 +33,38 @@ Raft does not have a global `--json` option. Parse each command's actual format:
|
|
|
31
33
|
| Other inspection/maintenance commands | Human-readable text or no output |
|
|
32
34
|
|
|
33
35
|
`list` emits nothing when there are no boxes. `stopAt` is the host Unix deadline
|
|
34
|
-
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
|
|
39
|
+
own disk size, such as `"20GiB"`. `note` is the box note object with
|
|
35
40
|
`purpose` and optional `owner` and `next`, or `null`. `info` uses native Incus keys rather
|
|
36
41
|
than a normalized Raft schema. `usage` includes `memoryBytes`, `memoryLimit`,
|
|
37
42
|
`cpuTime`, `effectiveCpus`, `filesystemUsedBytes`, `filesystemAvailableBytes`, and
|
|
38
|
-
`filesystemScope`; disk values describe the shared pool,
|
|
39
|
-
See [
|
|
43
|
+
`filesystemScope`; disk values describe the shared pool, or the box's own disk
|
|
44
|
+
when it has one. See [Box sizing and capacity](../reference/capacity.mdx) for the
|
|
45
|
+
advisory capacity fields.
|
|
40
46
|
|
|
41
47
|
These examples require `jq` on the controller:
|
|
42
48
|
|
|
43
49
|
```sh title="CONTROLLER: Parse records without mixing stderr"
|
|
44
50
|
raft list --location lab | jq -r 'select(.state == "Running") | .box'
|
|
45
|
-
raft
|
|
51
|
+
raft capacity --location lab --json | jq '{location, headroom, warnings}'
|
|
46
52
|
raft prune --location lab --older-than 7 | jq -r '.box'
|
|
47
53
|
```
|
|
48
54
|
|
|
55
|
+
`raft new` fills omitted options from the controller's
|
|
56
|
+
`~/.config/raft/config.json`, so the same script can create different boxes on
|
|
57
|
+
different controllers. Pass `--location`, `--ttl`, `--cpu`, `--memory`,
|
|
58
|
+
`--disk` and `--disposable` or `--persistent` in CI.
|
|
59
|
+
Hosts can also require box note fields; give `--purpose`, `--owner` and `--next`
|
|
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).
|
|
62
|
+
|
|
49
63
|
Do not redirect `2>&1` into machine-readable output. Recovery reports its allocated
|
|
50
64
|
handle on stderr before import finishes; only a successful completion prints the
|
|
51
|
-
final handle on stdout.
|
|
65
|
+
final handle on stdout. Once a day Raft may also print a release notice on
|
|
66
|
+
stderr. Set `RAFT_NO_UPDATE_CHECK=1` in jobs that pin a CLI version or compare
|
|
67
|
+
stderr.
|
|
52
68
|
|
|
53
69
|
## Exit codes and job results
|
|
54
70
|
|
|
@@ -58,6 +74,7 @@ final handle on stdout.
|
|
|
58
74
|
| Synchronous `exec` | Transport/command return code; preserves payload exit status when transport succeeds |
|
|
59
75
|
| Recognized runtime/configuration failure | `1`, with `raft: ...` on stderr |
|
|
60
76
|
| Argument parsing failure | `2`, with argparse diagnostics on stderr |
|
|
77
|
+
| `self-update --check` finds a newer release | `100` |
|
|
61
78
|
| Caught keyboard interruption | `130` |
|
|
62
79
|
|
|
63
80
|
SSH transport failures may return SSH's status rather than the payload's result.
|
|
@@ -73,7 +90,7 @@ running job's initial `ExecMainStatus=0` as completion. See
|
|
|
73
90
|
ready. Probe a harmless command before running setup. This Bash example waits up
|
|
74
91
|
to about a minute plus transport time, executes an uploaded project check, and
|
|
75
92
|
attempts to stop the persistent box on exit. It assumes `./my-project/check.sh`
|
|
76
|
-
exists
|
|
93
|
+
exists:
|
|
77
94
|
|
|
78
95
|
```bash title="CONTROLLER or trusted Linux CI runner"
|
|
79
96
|
set -euo pipefail
|
|
@@ -110,10 +127,10 @@ raft upload "$box" ./my-project /workspace/my-project --recursive
|
|
|
110
127
|
raft exec "$box" -- bash -lc 'cd /workspace/my-project && bash ./check.sh'
|
|
111
128
|
```
|
|
112
129
|
|
|
113
|
-
The stopped box remains available for inspection and still
|
|
130
|
+
The stopped box remains available for inspection and still uses disk space.
|
|
114
131
|
Destroy it after collecting needed files, or preview old stopped boxes with prune.
|
|
115
132
|
For disposable CI boxes, deletion on stop/expiry removes artifacts and snapshots;
|
|
116
|
-
download results before cleanup. A new box or a retry
|
|
133
|
+
download results before cleanup. A new box or a retry uses its own disk space.
|
|
117
134
|
|
|
118
135
|
Command readiness does not mean Docker or a web application is ready. Probe the
|
|
119
136
|
specific dependency (`docker info`, an HTTP health endpoint, or systemd status)
|
|
@@ -75,7 +75,7 @@ echo "Created fork: $copy"
|
|
|
75
75
|
- **Instance-only copy**: Uses Incus `--instance-only`. Filesystem data, installed packages, and `/workspace` files are copied; source snapshots are not inherited.
|
|
76
76
|
- **Immediate start**: `raft fork` launches the new container immediately with the specified TTL while the source remains stopped.
|
|
77
77
|
- **Fresh network identity**: The fork receives a new container name, a fresh MAC address on `rfbr0`, and an independent IP address.
|
|
78
|
-
- **
|
|
78
|
+
- **Disk**: The fork keeps the source disk. Both kinds fork in seconds as copy-on-write snapshots: a shared-pool source within the shared 60 GiB Btrfs pool, an own-disk source as a thin snapshot that reserves its full size in `raft-disks`.
|
|
79
79
|
|
|
80
80
|
Destroy the clone when finished to release capacity:
|
|
81
81
|
|
|
@@ -89,7 +89,13 @@ raft destroy "$copy"
|
|
|
89
89
|
copy=$(raft new --from "$box/pre-upgrade" --ttl 600)
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
The source box can be running because Raft copies the saved snapshot. The new box stays on the same host,
|
|
92
|
+
The source box can be running because Raft copies the saved snapshot. The new box stays on the same host, keeps the snapshot's CPU, RAM and disk unless `--cpu`, `--memory` or `--disk` overrides them, and has a fresh network identity. Copies are copy-on-write snapshots, so starting many boxes from one prepared snapshot is fast; on own disks each copy still reserves its full size. Filesystem secrets are copied too.
|
|
93
|
+
|
|
94
|
+
Add `--disk SIZE` to copy the snapshot onto an own disk of a new size, for example to give a full box more room:
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
bigger=$(raft new --from "$box/pre-upgrade" --disk 40GiB --ttl 600)
|
|
98
|
+
```
|
|
93
99
|
|
|
94
100
|
Remove a snapshot you no longer need:
|
|
95
101
|
|
|
@@ -104,4 +110,4 @@ copy=$(raft fork "$box" --ttl 600 --cpu 2 --memory 4GiB)
|
|
|
104
110
|
raft resume "$box" --ttl 600 --memory 2GiB
|
|
105
111
|
```
|
|
106
112
|
|
|
107
|
-
Omitted
|
|
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.
|