@microck/raft 0.5.0 → 0.7.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 +27 -15
- package/deploy/build-image.py +4 -2
- package/deploy/raft-expire.py +109 -31
- package/deploy/verify-controls.py +6 -6
- package/deploy/verify-features.py +329 -19
- package/deploy/verify-jobs.py +264 -0
- package/deploy/verify-limits.py +22 -3
- package/deploy/verify-live.py +31 -6
- package/deploy/verify-output.py +208 -0
- package/deploy/verify-release.py +33 -4
- package/deploy/verify-run.py +571 -0
- package/deploy/verify-self-update.py +9 -1
- package/deploy/verify-sync.py +122 -0
- package/docs/benchmarks/read-latency-2026-10-29.json +70 -0
- package/docs/boat-capabilities.md +60 -7
- package/docs/boat-parity-design.md +1 -0
- package/docs/boot-performance.mdx +21 -0
- package/docs/contract.md +88 -13
- package/docs/first-box.mdx +22 -19
- package/docs/guides/background-jobs.mdx +38 -31
- package/docs/guides/backup-recovery.mdx +4 -3
- package/docs/guides/ci-jobs.mdx +209 -0
- package/docs/guides/config-policy.mdx +109 -35
- package/docs/guides/files.mdx +2 -2
- package/docs/guides/index.mdx +31 -0
- package/docs/guides/long-running-services.mdx +9 -3
- package/docs/guides/meta.json +7 -2
- package/docs/guides/project-setup.mdx +1 -1
- package/docs/guides/scripting-ci.mdx +36 -14
- package/docs/guides/snapshots-forks.mdx +4 -2
- package/docs/guides/troubleshooting.mdx +19 -2
- package/docs/guides/upgrades.mdx +11 -9
- package/docs/index.mdx +26 -28
- package/docs/maintainers/index.mdx +18 -0
- package/docs/maintainers/meta.json +12 -0
- package/docs/meta.json +5 -9
- package/docs/operations.mdx +17 -10
- package/docs/publishing.md +14 -4
- package/docs/reference/capacity.mdx +2 -1
- package/docs/reference/cli.md +135 -43
- package/docs/reference/configuration.mdx +10 -6
- package/docs/reference/meta.json +3 -1
- package/docs/releases/0.2.0.md +1 -0
- package/docs/releases/0.2.1.md +1 -0
- package/docs/releases/0.3.0.md +1 -0
- package/docs/releases/0.4.0.md +1 -0
- package/docs/releases/0.5.0.md +1 -0
- package/docs/releases/0.6.0.md +17 -0
- package/docs/releases/0.7.0.md +23 -0
- package/docs/releases/index.mdx +21 -0
- package/docs/releases/meta.json +15 -0
- package/docs/requirements.mdx +1 -1
- package/docs/reviews/docs-audit-2026-10-24.md +92 -0
- package/docs/reviews/performance-audit-2026-10-29.md +90 -0
- package/docs/site-development.md +1 -0
- package/docs/verification.mdx +22 -4
- package/package.json +3 -1
- package/raft.py +526 -193
- package/raft_files.py +13 -1
- package/raft_jobs.py +390 -0
- package/raft_output.py +166 -0
- package/raft_run.py +330 -0
- package/skills/raft-cli/SKILL.md +78 -182
|
@@ -5,11 +5,11 @@ description: Run detached commands with systemd logging and process management.
|
|
|
5
5
|
|
|
6
6
|
# Background jobs
|
|
7
7
|
|
|
8
|
-
Raft runs detached
|
|
8
|
+
Raft runs detached commands under guest systemd and captures their results with a transient host supervisor.
|
|
9
9
|
|
|
10
10
|
## Target workspace handle
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Launching a job requires a running workspace. Select one from `raft list` (see [First working box](../first-box.mdx)):
|
|
13
13
|
|
|
14
14
|
```sh title="CONTROLLER: Select running workspace"
|
|
15
15
|
raft list
|
|
@@ -33,49 +33,56 @@ The command returns a 32-hex systemd unit name prefixed with `rfcmd-` (such as `
|
|
|
33
33
|
|
|
34
34
|
## Checking job status
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
`raft status "$box" "$job"` returns a retained JSON record:
|
|
37
37
|
|
|
38
|
-
```
|
|
39
|
-
|
|
38
|
+
```json
|
|
39
|
+
{"job":"rfcmd-...","box":"lab:rf-...","state":"completed","exitCode":23,"startedAt":1793280000,"finishedAt":1793280010,"droppedBytes":0}
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Because Raft runs detached commands with `RemainAfterExit=yes`, completed jobs retain their exit record:
|
|
50
|
-
```text
|
|
51
|
-
ActiveState=active
|
|
52
|
-
SubState=exited
|
|
53
|
-
ExecMainStatus=0
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
<Callout type="info" title="Exit status vs CLI return code">
|
|
57
|
-
`ExecMainStatus` reports the command payload exit code inside the container. The `raft status` command returns exit code 0 when the query succeeds. It queries unit state and does not report process PIDs or start timestamps.
|
|
58
|
-
</Callout>
|
|
59
|
-
|
|
60
|
-
## Viewing job logs
|
|
42
|
+
States are `starting`, `running`, `completed`, `cancelled`, `interrupted` and
|
|
43
|
+
`failed`. `completed` means the command finished, not that it succeeded; inspect
|
|
44
|
+
`exitCode`. An interrupted job has no confirmed payload exit code. A transport interruption can leave the guest command running;
|
|
45
|
+
inspect or cancel it before rerunning the payload. Timestamps
|
|
46
|
+
are host Unix seconds. A successful status query itself returns zero.
|
|
61
47
|
|
|
62
|
-
|
|
48
|
+
## Viewing retained output
|
|
63
49
|
|
|
64
|
-
```sh
|
|
50
|
+
```sh
|
|
65
51
|
raft logs "$box" "$job"
|
|
66
52
|
```
|
|
67
53
|
|
|
68
|
-
|
|
54
|
+
A host supervisor retains the first 1 MiB of combined stdout and stderr. Further
|
|
55
|
+
output is drained and counted in `droppedBytes` when the job ends. Logs and final
|
|
56
|
+
status remain readable after the workspace stops or is deleted; keep its exact
|
|
57
|
+
original handle. They are not copied by workspace snapshots or forks.
|
|
69
58
|
|
|
70
|
-
|
|
59
|
+
Results live in the root-private host directory `/var/lib/raft-jobs`. They expire
|
|
60
|
+
seven days after completion. Job commands prune expired records on access, so an
|
|
61
|
+
idle host may retain expired files until the next access. The store holds at most
|
|
62
|
+
512 records and refuses new jobs when full instead of evicting unexpired results.
|
|
63
|
+
Unreadable records remain on the host for inspection and count toward the limit.
|
|
64
|
+
They do not block other jobs. A query for a damaged record reports its host path;
|
|
65
|
+
inspect that directory and its retained output before deciding how to recover it.
|
|
66
|
+
Output can contain secrets; this is host-local retention, not an off-host backup.
|
|
71
67
|
|
|
72
|
-
|
|
68
|
+
## Cancelling jobs
|
|
73
69
|
|
|
74
|
-
```sh
|
|
70
|
+
```sh
|
|
75
71
|
raft cancel "$box" "$job"
|
|
76
72
|
```
|
|
77
73
|
|
|
78
|
-
|
|
74
|
+
Cancellation stops the guest systemd control group. Retained output remains
|
|
75
|
+
available. If a job is still starting and its guest unit is not yet registered,
|
|
76
|
+
cancellation reports an error; inspect its state before retrying. Unknown job
|
|
77
|
+
IDs and IDs belonging to another workspace are rejected. Cancellation waits up to
|
|
78
|
+
120 seconds. If it times out, the cancellation request remains recorded; inspect
|
|
79
|
+
the job and retry cancellation.
|
|
80
|
+
|
|
81
|
+
Host reboot or guest shutdown interrupts active jobs. The next inspection marks
|
|
82
|
+
an absent supervisor as interrupted rather than assuming success. Completed
|
|
83
|
+
results survive host reboot. This development implementation has no fallback to
|
|
84
|
+
old guest-only journals; inspect old jobs with their controller version before
|
|
85
|
+
upgrading.
|
|
79
86
|
|
|
80
87
|
## Extending workspace lifetime
|
|
81
88
|
|
|
@@ -9,7 +9,8 @@ Raft exports portable workspace archives and restores them onto compatible hosts
|
|
|
9
9
|
|
|
10
10
|
## Target workspace handle
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Use a **persistent** workspace. Stop it before backup; stopping a disposable box
|
|
13
|
+
deletes it and its snapshots, so copy files out instead.
|
|
13
14
|
|
|
14
15
|
```sh title="CONTROLLER: Select and stop target workspace"
|
|
15
16
|
raft list
|
|
@@ -35,7 +36,7 @@ Backup archives contain the entire guest filesystem, including credentials, SSH
|
|
|
35
36
|
|
|
36
37
|
Raft enforces safeguards during export:
|
|
37
38
|
|
|
38
|
-
- **Private permissions**:
|
|
39
|
+
- **Private permissions**: Raft creates the staged archive with mode `0600` and publishes it privately. Choose a new destination path; do not pre-create the file.
|
|
39
40
|
- **Atomic link publication**: After export and `fsync`, Raft publishes the archive using `os.link(archive.name, destination)`. Hard-link support is checked at final publication rather than preflight; export can succeed while publication fails if the filesystem lacks hard-link support.
|
|
40
41
|
- **No overwriting**: The destination file must not already exist.
|
|
41
42
|
- **Lifecycle lock**: Export holds `/run/lock/raft-incus.lock`. Expiry checks for other containers on the host are delayed while the lock is held.
|
|
@@ -53,7 +54,7 @@ echo "Recovered workspace: $recovered"
|
|
|
53
54
|
|
|
54
55
|
- **Architecture compatibility**: Target host must match the native CPU architecture (ARM64 to ARM64, AMD64 to AMD64). Cross-architecture recovery is not supported.
|
|
55
56
|
- **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
|
+
- **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
58
|
- **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
59
|
- **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
60
|
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Disposable CI jobs
|
|
3
|
+
description: Run trusted builds in temporary Raft boxes, collect results, and clean up with a bounded lifetime.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Disposable CI jobs
|
|
7
|
+
|
|
8
|
+
`raft run` runs one foreground command in a fresh disposable box. It creates the
|
|
9
|
+
box, waits for command readiness, optionally uploads source, streams command
|
|
10
|
+
output, collects requested result directories, and attempts ownership-checked
|
|
11
|
+
cleanup even after failure or cancellation. It does not register a GitHub
|
|
12
|
+
self-hosted runner or replace your CI scheduler.
|
|
13
|
+
|
|
14
|
+
<Callout type="warn" title="Unreleased: requires the 0.7.0 development code">
|
|
15
|
+
The published release is 0.6.0, which does not include `raft run`. As checked on
|
|
16
|
+
2026-10-24, no 0.7.0 npm package or GitHub release assets are available. Use the
|
|
17
|
+
[stable scripting workflow](./scripting-ci.mdx) unless you intend to test a reviewed
|
|
18
|
+
source revision. Do not install a nonexistent 0.7.0 package.
|
|
19
|
+
</Callout>
|
|
20
|
+
|
|
21
|
+
For development, run `python3 /path/to/raft.py run` from the reviewed checkout.
|
|
22
|
+
The CI example below pins the full source commit instead of an unpublished tag.
|
|
23
|
+
|
|
24
|
+
## Trust boundary
|
|
25
|
+
|
|
26
|
+
Use dedicated hosts and trusted jobs only. The controller's SSH identity has
|
|
27
|
+
passwordless sudo and controls the host; it is not a scoped, per-job CI token.
|
|
28
|
+
Boxes are unprivileged Incus containers sharing the host kernel, not separate
|
|
29
|
+
kernel VMs. Do not expose this identity to unreviewed pull requests, fork jobs,
|
|
30
|
+
or a `pull_request_target` workflow that checks out attacker-controlled code.
|
|
31
|
+
|
|
32
|
+
Raft does not install or register a runner, configure repository secrets, forward
|
|
33
|
+
your SSH agent, copy controller environment variables, or buy cloud capacity.
|
|
34
|
+
The operator must approve and configure existing host access separately. Never
|
|
35
|
+
copy a host private key or a GitHub token into the guest.
|
|
36
|
+
|
|
37
|
+
## One command
|
|
38
|
+
|
|
39
|
+
```sh title="CONTROLLER: Git working tree"
|
|
40
|
+
RAFT_NO_UPDATE_CHECK=1 raft run \
|
|
41
|
+
--location lab --timeout 600 --cpu 2 --memory 4GiB \
|
|
42
|
+
--purpose "Project tests" --owner "Local CI" --next "Delete after collection" \
|
|
43
|
+
--push . --pull /workspace/project/reports ./ci-reports \
|
|
44
|
+
-- bash -lc 'set -euo pipefail; mkdir -p reports; npm ci; npm test 2>&1 | tee reports/test.log'
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The controller needs Linux, Python 3.11.8+, OpenSSH, Git for `--push`, a configured
|
|
48
|
+
Raft location, verified SSH host keys, batch authentication, and the current host
|
|
49
|
+
expiry worker. Ordinary image/host sizing and note policies still apply. `run`
|
|
50
|
+
always forces disposable behavior, regardless of controller lifetime defaults.
|
|
51
|
+
It never retries the payload command.
|
|
52
|
+
|
|
53
|
+
- stdout/stderr stream the payload; lifecycle messages and elapsed time go to
|
|
54
|
+
stderr. There is no cost estimate or JSON result protocol.
|
|
55
|
+
- `--timeout` is one total deadline for Git filtering, creation, readiness, upload
|
|
56
|
+
and execution, not a fresh timeout for each step. Default: 600 seconds; range:
|
|
57
|
+
1 to 2,591,880 seconds.
|
|
58
|
+
- `--push` includes tracked and unignored untracked files from the chosen Git
|
|
59
|
+
working tree, including working-tree edits. It excludes `.git` at every level
|
|
60
|
+
and refuses submodules. Tracked files remain included even if ignored later:
|
|
61
|
+
this is not a secret scanner. Deleted tracked files are absent.
|
|
62
|
+
- `--push-all` explicitly copies the complete directory, including hidden and
|
|
63
|
+
ignored files and Git metadata. Inspect it for credentials first. This is the
|
|
64
|
+
opt-in alternative for non-Git or explicitly staged trees; it is not a silent
|
|
65
|
+
fallback when Git fails.
|
|
66
|
+
- Uploads default to `/workspace/project`; without an upload, commands default
|
|
67
|
+
to `/workspace`. `--cwd` overrides this. An upload destination must not exist,
|
|
68
|
+
and its parent must already exist. Raft never merges into a stale checkout.
|
|
69
|
+
- Repeat `--pull GUEST_DIRECTORY NEW_LOCAL_DIRECTORY` to collect directories,
|
|
70
|
+
including after a failing payload. Local parents must exist; destinations
|
|
71
|
+
must be new, including no existing symlinks. Files can be placed in a result
|
|
72
|
+
directory. Collection happens before deletion, with a shared 30-second grace.
|
|
73
|
+
A failed pull does not stop later pulls unless that grace has expired.
|
|
74
|
+
- Exit status preserves a failing payload, including signal-style statuses.
|
|
75
|
+
Job timeout returns 124, Ctrl-C 130, SIGTERM 143. Setup/collection/cleanup
|
|
76
|
+
failure returns 1 when there is no earlier failing status. A successful
|
|
77
|
+
payload is not reported successful when cleanup cannot be confirmed.
|
|
78
|
+
|
|
79
|
+
The host receives a unique ownership marker and a fixed expiry of timeout + 120
|
|
80
|
+
seconds as part of creation, before startup. Cleanup checks ownership under the
|
|
81
|
+
host lifecycle lock and has a separate 30-second grace. Repeated cancellation is
|
|
82
|
+
ignored during these bounded collection/cleanup attempts. No lease renewal or
|
|
83
|
+
`--keep` mode is implemented.
|
|
84
|
+
|
|
85
|
+
If creation was not acknowledged, cleanup first records that run's cancellation
|
|
86
|
+
under the same host lifecycle lock. Creation checks that record before allocating,
|
|
87
|
+
so a queued SSH request cannot start the box after cleanup has confirmed absence.
|
|
88
|
+
These empty per-run records live in the root-only `/run/raft-ci-cancelled`
|
|
89
|
+
directory, use no-follow operations, and remain until host reboot, when queued
|
|
90
|
+
processes also disappear. Ordinary acknowledged runs leave no cancellation file.
|
|
91
|
+
|
|
92
|
+
If the controller is killed before cleanup, SSH is lost, or cleanup fails, inspect
|
|
93
|
+
the exact handle printed on stderr. A disconnected create may still finish when
|
|
94
|
+
cancellation could not be recorded. The disposable expiry worker is a backstop,
|
|
95
|
+
not a guarantee of instantaneous deletion: an unreachable host, stopped worker or
|
|
96
|
+
held lifecycle lock can delay it. Do not blindly rerun an uncertain job or assume
|
|
97
|
+
its side effects were rolled back.
|
|
98
|
+
|
|
99
|
+
## Prepared templates instead of shared mutable caches
|
|
100
|
+
|
|
101
|
+
Prepare dependencies and tool versions in a persistent base box, stop it, then
|
|
102
|
+
create a named snapshot. See [project setup](./project-setup.mdx) and
|
|
103
|
+
[snapshots](./snapshots-forks.mdx). Keep the source box and snapshot available:
|
|
104
|
+
|
|
105
|
+
```sh title="CONTROLLER: Use an existing trusted snapshot"
|
|
106
|
+
raft run --from lab:rf-REPLACE_WITH_BASE_ID/ci-base --timeout 900 \
|
|
107
|
+
--purpose "Cached build" --owner "CI" --next "Delete after tests" \
|
|
108
|
+
--push . -- bash -lc 'npm ci --prefer-offline && npm test'
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The template stays on its source host and retains its size unless explicitly
|
|
112
|
+
overridden. Put reusable caches outside the upload destination, which must be
|
|
113
|
+
new. Each job gets an independent copy; its mutations do not update the template
|
|
114
|
+
or another job's cache. Refresh templates deliberately when dependencies change.
|
|
115
|
+
|
|
116
|
+
Raft snapshots retain filesystem contents, including credentials and Docker
|
|
117
|
+
storage. They do not save process memory. Inspect and sanitize the base before
|
|
118
|
+
sharing it with jobs. Do not assume Boat's selective snapshot/cache rules apply
|
|
119
|
+
to Raft's whole-filesystem copies.
|
|
120
|
+
|
|
121
|
+
## GitHub Actions orchestration example
|
|
122
|
+
|
|
123
|
+
The following is an example for a separate trusted project, not a change to
|
|
124
|
+
Raft's own runner fleet. An ordinary `ubuntu-latest` job invokes Raft over SSH.
|
|
125
|
+
Before using it, an operator must approve storing a dedicated CI host key as
|
|
126
|
+
`RAFT_SSH_KEY`, verified host-key lines as `RAFT_KNOWN_HOSTS`, and the location
|
|
127
|
+
inventory JSON as `RAFT_INVENTORY`. The inventory's SSH target must use that key
|
|
128
|
+
and its image fingerprint must be pinned. Never generate trust with an unchecked
|
|
129
|
+
`ssh-keyscan` or disable host-key checking. Restrict access to these secrets.
|
|
130
|
+
|
|
131
|
+
The example pins development commit `7d678bb079a9b2dcde4e2eb9e4cd0468d00f50f3` in a
|
|
132
|
+
separate Python environment. Review that code and its unreleased status
|
|
133
|
+
before use; do not silently replace it with a moving branch.
|
|
134
|
+
|
|
135
|
+
```yaml title="EXAMPLE: .github/workflows/trusted-build.yml"
|
|
136
|
+
name: Trusted Raft build
|
|
137
|
+
on:
|
|
138
|
+
push:
|
|
139
|
+
branches: [main]
|
|
140
|
+
workflow_dispatch:
|
|
141
|
+
permissions:
|
|
142
|
+
contents: read
|
|
143
|
+
jobs:
|
|
144
|
+
test:
|
|
145
|
+
runs-on: ubuntu-latest
|
|
146
|
+
timeout-minutes: 20
|
|
147
|
+
env:
|
|
148
|
+
RAFT_NO_UPDATE_CHECK: '1'
|
|
149
|
+
steps:
|
|
150
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
|
151
|
+
with:
|
|
152
|
+
persist-credentials: false
|
|
153
|
+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
|
|
154
|
+
with:
|
|
155
|
+
python-version: '3.14'
|
|
156
|
+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
|
|
157
|
+
with:
|
|
158
|
+
node-version: '24'
|
|
159
|
+
- name: Install reviewed development controller
|
|
160
|
+
run: |
|
|
161
|
+
python -m venv "$RUNNER_TEMP/raft-cli"
|
|
162
|
+
"$RUNNER_TEMP/raft-cli/bin/pip" install "git+https://github.com/Microck/raft.git@7d678bb079a9b2dcde4e2eb9e4cd0468d00f50f3"
|
|
163
|
+
echo "$RUNNER_TEMP/raft-cli/bin" >> "$GITHUB_PATH"
|
|
164
|
+
- name: Configure approved existing access
|
|
165
|
+
env:
|
|
166
|
+
SSH_KEY: ${{ secrets.RAFT_SSH_KEY }}
|
|
167
|
+
KNOWN_HOSTS: ${{ secrets.RAFT_KNOWN_HOSTS }}
|
|
168
|
+
INVENTORY: ${{ secrets.RAFT_INVENTORY }}
|
|
169
|
+
run: |
|
|
170
|
+
umask 077
|
|
171
|
+
mkdir -p ~/.ssh ~/.config/raft
|
|
172
|
+
printf '%s\n' "$SSH_KEY" > ~/.ssh/id_ed25519
|
|
173
|
+
printf '%s\n' "$KNOWN_HOSTS" > ~/.ssh/known_hosts
|
|
174
|
+
printf '%s\n' "$INVENTORY" > ~/.config/raft/incus.json
|
|
175
|
+
printf 'Host *\n BatchMode yes\n StrictHostKeyChecking yes\n IdentitiesOnly yes\n' > ~/.ssh/config
|
|
176
|
+
- name: Run remote tests and collect logs
|
|
177
|
+
run: |
|
|
178
|
+
raft run --location ci --timeout 900 --cpu 2 --memory 4GiB \
|
|
179
|
+
--purpose "Trusted project tests" --owner "GitHub Actions" --next "Delete after tests" \
|
|
180
|
+
--push . --pull /workspace/project/reports ./ci-reports \
|
|
181
|
+
-- bash -lc 'set -euo pipefail; mkdir -p reports; npm ci; npm test 2>&1 | tee reports/test.log'
|
|
182
|
+
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
|
183
|
+
if: always()
|
|
184
|
+
with:
|
|
185
|
+
name: test-reports
|
|
186
|
+
path: ci-reports/
|
|
187
|
+
if-no-files-found: warn
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
No checkout credential is needed in the guest. The secret files are outside the
|
|
191
|
+
uploaded tree, and `.git` is excluded. Set a CI job limit longer than Raft's
|
|
192
|
+
command deadline plus collection and cleanup grace. A forcibly terminated CI
|
|
193
|
+
runner cannot perform final cleanup; check the host expiry service and inventory.
|
|
194
|
+
|
|
195
|
+
## Comparison and cost
|
|
196
|
+
|
|
197
|
+
[Boat's CI guide](https://docs.boat.dev/ci) uses the same broad pattern: an existing
|
|
198
|
+
CI job orchestrates a disposable remote workspace. Boat also provides a managed
|
|
199
|
+
`run` command, scoped API keys, renewable leases and a different VM/snapshot
|
|
200
|
+
model. Raft implements a fixed lifetime on operator-owned Incus hosts and uses
|
|
201
|
+
host-level SSH access; these are meaningful differences, not interchangeable
|
|
202
|
+
security or billing guarantees.
|
|
203
|
+
|
|
204
|
+
There is no universal “10x cheaper” claim for Raft. Compare the complete host,
|
|
205
|
+
storage, idle capacity, operations and orchestration-runner cost. Standard
|
|
206
|
+
GitHub-hosted runners are already free for public repositories; remote capacity
|
|
207
|
+
can still help with specific hardware, prepared environments or isolation from
|
|
208
|
+
other trusted jobs. See [Boat pricing](https://docs.boat.dev/pricing) and
|
|
209
|
+
[GitHub Actions billing](https://docs.github.com/en/billing/concepts/product-billing/github-actions).
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: Defaults and
|
|
3
|
-
description: Set controller defaults for new boxes
|
|
2
|
+
title: Defaults and host policies
|
|
3
|
+
description: Set controller defaults for new boxes, require box note fields on a host and delete boxes left stopped.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Defaults and
|
|
6
|
+
# Defaults and host policies
|
|
7
7
|
|
|
8
8
|
Raft has two kinds of settings:
|
|
9
9
|
|
|
10
10
|
| Setting | Stored in | Applies to | Change with |
|
|
11
11
|
| --- | --- | --- | --- |
|
|
12
12
|
| Defaults for `new` | `~/.config/raft/config.json` on the controller | That controller only | `raft config set` and `raft config unset` |
|
|
13
|
-
| Required box note fields | Incus project `raft` on each host | Every controller that uses the host | `raft policy` |
|
|
13
|
+
| Required box note fields | Incus project `raft` on each host | Every controller that uses the host | `raft policy --require` |
|
|
14
|
+
| Stopped-box retention | Incus project `raft` on each host | Every box on the host | `raft policy --delete-stopped-after` |
|
|
14
15
|
|
|
15
|
-
`raft config` prints both: the effective defaults with their source, and
|
|
16
|
-
location's
|
|
16
|
+
`raft config` prints both kinds: the effective defaults with their source, and
|
|
17
|
+
each location's policies.
|
|
17
18
|
|
|
18
19
|
```sh title="CONTROLLER: Inspect defaults and policies"
|
|
19
20
|
raft config
|
|
@@ -42,6 +43,10 @@ raft config
|
|
|
42
43
|
"disk": {
|
|
43
44
|
"value": "shared",
|
|
44
45
|
"source": "built-in"
|
|
46
|
+
},
|
|
47
|
+
"disposable": {
|
|
48
|
+
"value": true,
|
|
49
|
+
"source": "config"
|
|
45
50
|
}
|
|
46
51
|
},
|
|
47
52
|
"policy": {
|
|
@@ -50,7 +55,10 @@ raft config
|
|
|
50
55
|
"purpose",
|
|
51
56
|
"owner"
|
|
52
57
|
],
|
|
53
|
-
"
|
|
58
|
+
"requireChanged": "2026-10-21T09:12:44Z by ubuntu from me@laptop",
|
|
59
|
+
"deleteStoppedAfterHours": 48,
|
|
60
|
+
"deleteStoppedAfterSince": 1792573964,
|
|
61
|
+
"deleteStoppedAfterChanged": "2026-10-21T09:12:44Z by ubuntu from me@laptop"
|
|
54
62
|
}
|
|
55
63
|
}
|
|
56
64
|
}
|
|
@@ -61,33 +69,42 @@ names the location when a host is unreachable.
|
|
|
61
69
|
|
|
62
70
|
## Defaults for new
|
|
63
71
|
|
|
64
|
-
Set a default for any of `location`, `ttl`, `cpu`, `memory` and
|
|
72
|
+
Set a default for any of `location`, `ttl`, `cpu`, `memory`, `disk` and
|
|
73
|
+
`disposable`:
|
|
65
74
|
|
|
66
|
-
```sh title="CONTROLLER: Prefer bigger, longer boxes on paris"
|
|
75
|
+
```sh title="CONTROLLER: Prefer bigger, longer, temporary boxes on paris"
|
|
67
76
|
raft config set location paris
|
|
68
77
|
raft config set ttl 3600
|
|
69
78
|
raft config set cpu 4
|
|
70
79
|
raft config set memory 16GiB
|
|
71
80
|
raft config set disk 20GiB
|
|
81
|
+
raft config set disposable true
|
|
72
82
|
raft config unset cpu
|
|
73
83
|
```
|
|
74
84
|
|
|
75
85
|
Each command prints the old and new value. `raft new` takes an explicit flag
|
|
76
86
|
first, then the configured default, then the built-in default (first configured
|
|
77
|
-
location, 600 seconds, 1 CPU, 2 GiB, the shared pool). `resume`,
|
|
78
|
-
`extend` and `recover` take no defaults: their TTL and target are
|
|
79
|
-
choices, and omitted sizes on resume and fork keep the source limits.
|
|
87
|
+
location, 600 seconds, 1 CPU, 2 GiB, the shared pool, persistent). `resume`,
|
|
88
|
+
`fork`, `extend` and `recover` take no defaults: their TTL and target are
|
|
89
|
+
deliberate choices, and omitted sizes on resume and fork keep the source limits.
|
|
90
|
+
|
|
91
|
+
With `disposable true`, every `raft new` box is deleted, with its snapshots, when
|
|
92
|
+
it stops or expires. `raft new --persistent` keeps one box anyway, and
|
|
93
|
+
`--disposable` overrides `disposable false` the same way. The default applies to
|
|
94
|
+
`new --from` too: a snapshot has no lifecycle of its own. A fork stays
|
|
95
|
+
persistent unless it gets `--disposable`, whatever the default says, like every
|
|
96
|
+
other fork setting.
|
|
80
97
|
|
|
81
98
|
Size defaults (`cpu`, `memory`, `disk`) apply only where the built-in sizes do:
|
|
82
99
|
to boxes created from the image. A box from `new --from` keeps the snapshot's
|
|
83
100
|
location, CPU, RAM and disk, and a fork keeps its source's, whatever
|
|
84
101
|
`config.json` says. Only a flag changes them:
|
|
85
102
|
|
|
86
|
-
| Command | CPU and RAM | Disk |
|
|
87
|
-
| --- | --- | --- |
|
|
88
|
-
| `new` | Flag, then config, then 1 CPU / 2 GiB | Flag, then config, then the shared pool |
|
|
89
|
-
| `new --from` | Flag, then the snapshot's | `--disk SIZE`, then the snapshot's |
|
|
90
|
-
| `fork` | Flag, then the source's | The source's |
|
|
103
|
+
| Command | CPU and RAM | Disk | Disposable |
|
|
104
|
+
| --- | --- | --- | --- |
|
|
105
|
+
| `new` | Flag, then config, then 1 CPU / 2 GiB | Flag, then config, then the shared pool | Flag, then config, then persistent |
|
|
106
|
+
| `new --from` | Flag, then the snapshot's | `--disk SIZE`, then the snapshot's | Flag, then config, then persistent |
|
|
107
|
+
| `fork` | Flag, then the source's | The source's | `--disposable`, else persistent |
|
|
91
108
|
|
|
92
109
|
A `disk` default needs own box disks on the target host. On a location without
|
|
93
110
|
the `raft-disks` pool, or with too little unreserved space, `raft new` fails with
|
|
@@ -107,9 +124,9 @@ path and the key; Raft never ignores part of the file. Repair a bad key with
|
|
|
107
124
|
`raft config set KEY VALUE` or `raft config unset KEY`. Writes are atomic, keep
|
|
108
125
|
mode `0600` and remove the file when no default remains.
|
|
109
126
|
|
|
110
|
-
Scripts and CI jobs that need stable
|
|
111
|
-
`--cpu`, `--memory` and `--
|
|
112
|
-
controllers.
|
|
127
|
+
Scripts and CI jobs that need stable boxes should pass `--location`, `--ttl`,
|
|
128
|
+
`--cpu`, `--memory`, `--disk` and `--disposable` or `--persistent` explicitly,
|
|
129
|
+
because defaults differ between controllers.
|
|
113
130
|
|
|
114
131
|
## Required box note fields
|
|
115
132
|
|
|
@@ -118,7 +135,7 @@ fields. `purpose` is always part of a note, so a policy must include it:
|
|
|
118
135
|
|
|
119
136
|
```sh title="CONTROLLER: Require an owner on lab"
|
|
120
137
|
raft policy lab --require purpose,owner
|
|
121
|
-
raft policy lab --
|
|
138
|
+
raft policy lab --require none
|
|
122
139
|
```
|
|
123
140
|
|
|
124
141
|
With that policy, a command that would leave a box without an owner fails before
|
|
@@ -151,29 +168,86 @@ so a policy change cannot race a creation. That transaction checks the
|
|
|
151
168
|
requested size against the host and reserves own-disk space first; any refusal
|
|
152
169
|
comes before Incus creates the box.
|
|
153
170
|
|
|
171
|
+
## Delete boxes left stopped
|
|
172
|
+
|
|
173
|
+
A retention policy makes a host delete boxes that stay stopped too long, with
|
|
174
|
+
their snapshots. Use it when agents create boxes and forget them:
|
|
175
|
+
|
|
176
|
+
```sh title="CONTROLLER: Delete boxes stopped for 48 hours on lab"
|
|
177
|
+
raft policy lab --delete-stopped-after 48
|
|
178
|
+
raft policy lab --delete-stopped-after never
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The host expiry worker applies it every 15 seconds, and on `raft gc`, even when
|
|
182
|
+
no controller is online. It deletes a stopped box once the given hours have
|
|
183
|
+
passed since the later of:
|
|
184
|
+
|
|
185
|
+
- the box's stop, recorded in `user.raft.stopped-at` by `raft stop` and expiry;
|
|
186
|
+
- the policy change, recorded in `user.raft.delete-stopped-after.since`.
|
|
187
|
+
|
|
188
|
+
So a new or shorter policy never deletes a box sooner than its hours after the
|
|
189
|
+
change, including boxes that were stopped long before. Lengthening it or setting
|
|
190
|
+
it again restarts the grace period the same way.
|
|
191
|
+
|
|
192
|
+
| Box | What retention does |
|
|
193
|
+
| --- | --- |
|
|
194
|
+
| Running, including one whose TTL has not expired | Nothing |
|
|
195
|
+
| Stopped with `raft stop` or expiry | Deleted HOURS after the later of the stop and the policy change |
|
|
196
|
+
| Stopped another way: a poweroff inside the box, a native `incus stop`, a failed start or `raft recover` | The worker records the time it first sees the box stopped, and the clock starts then |
|
|
197
|
+
| Resumed before its deadline | Nothing; `resume` clears the stop time, and the next stop starts a new clock |
|
|
198
|
+
|
|
199
|
+
`raft list` shows when each stopped box will go, as `deleteAt`:
|
|
200
|
+
|
|
201
|
+
```sh title="CONTROLLER: Stopped boxes and their deletion times"
|
|
202
|
+
raft list --location lab | jq -r 'select(.deleteAt != "") | "\(.box) \(.deleteAt | tonumber | todate)"'
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Retention deletes the whole box. Keep anything you need with
|
|
206
|
+
[`raft backup`](./backup-recovery.mdx) before the deadline, or resume the box to
|
|
207
|
+
reset its clock. Snapshots live inside the box and are deleted with it.
|
|
208
|
+
|
|
209
|
+
Retention needs the current host worker. On a host deployed by an older release,
|
|
210
|
+
`--delete-stopped-after HOURS` fails with
|
|
211
|
+
`Host expiry worker differs from this CLI; deploy the current Raft checkout first`
|
|
212
|
+
and changes nothing; redeploy the host, see
|
|
213
|
+
[Upgrades and image changes](./upgrades.mdx). Clearing it with `never` works on
|
|
214
|
+
any worker.
|
|
215
|
+
|
|
216
|
+
`raft prune --older-than DAYS` remains a manual, previewed cleanup that works
|
|
217
|
+
with or without a retention policy.
|
|
218
|
+
|
|
154
219
|
## Audit trail
|
|
155
220
|
|
|
156
|
-
`raft policy` prints
|
|
157
|
-
`lab: required note fields none -> purpose,owner
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
221
|
+
`raft policy` prints each change, for example
|
|
222
|
+
`lab: required note fields none -> purpose,owner` or
|
|
223
|
+
`lab: delete stopped boxes after never -> 48 hours`. The host records the last
|
|
224
|
+
change of each policy in a `.changed` project key (UTC time, the SSH account and
|
|
225
|
+
the controller's self-reported user and hostname) and writes a `raft` entry to
|
|
226
|
+
the host journal. The worker adds a journal entry for every box retention
|
|
227
|
+
deletes:
|
|
161
228
|
|
|
162
|
-
```sh title="HOST: Review policy changes"
|
|
229
|
+
```sh title="HOST: Review policy changes and retention deletions"
|
|
163
230
|
sudo incus project get raft user.raft.require
|
|
164
231
|
sudo incus project get raft user.raft.require.changed
|
|
232
|
+
sudo incus project get raft user.raft.delete-stopped-after
|
|
233
|
+
sudo incus project get raft user.raft.delete-stopped-after.changed
|
|
165
234
|
sudo journalctl -t raft
|
|
166
235
|
```
|
|
167
236
|
|
|
168
237
|
## Limits
|
|
169
238
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
- Anyone with SSH and sudo on the host can change or remove
|
|
173
|
-
boxes with native `incus` commands that skip
|
|
174
|
-
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
239
|
+
Host policies are a guardrail for agents, not a security boundary:
|
|
240
|
+
|
|
241
|
+
- Anyone with SSH and sudo on the host can change or remove a policy, or create
|
|
242
|
+
boxes with native `incus` commands that skip the note policy.
|
|
243
|
+
- Retention trusts `user.raft.stopped-at`. The worker clears it when it sees a
|
|
244
|
+
box running, but a native `incus start` and stop between two worker runs (15
|
|
245
|
+
seconds apart) keeps the old stop time, so the box can be deleted on the next
|
|
246
|
+
run. Use `raft resume`, which clears the stop time.
|
|
247
|
+
- Controllers older than the release that added note policies do not check
|
|
248
|
+
them. Upgrade every controller that uses a host before relying on one. The
|
|
249
|
+
host worker enforces retention whatever the controller version.
|
|
250
|
+
- The controller identity in the `.changed` keys is reported by the controller
|
|
251
|
+
and is not verified. The SSH account comes from `sudo`.
|
|
178
252
|
- Raft checks that required fields are present and nonempty, not that their text
|
|
179
253
|
is accurate.
|
package/docs/guides/files.mdx
CHANGED
|
@@ -5,11 +5,11 @@ description: Transfer files between your controller machine and running Raft wor
|
|
|
5
5
|
|
|
6
6
|
# File transfer
|
|
7
7
|
|
|
8
|
-
Upload and download individual files between your
|
|
8
|
+
Upload and download individual files between your controller and a box over verified host SSH and Incus instance APIs.
|
|
9
9
|
|
|
10
10
|
## Target workspace handle
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Use a running box for all examples below. Individual-file transfers also work while stopped; recursive directory transfers require a running box. Select one from `raft list` (see [First working box](../first-box.mdx)):
|
|
13
13
|
|
|
14
14
|
```sh title="CONTROLLER: Select running workspace"
|
|
15
15
|
raft list
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Task guides
|
|
3
|
+
description: Pick a workflow after setting up your first Raft host.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Task guides
|
|
7
|
+
|
|
8
|
+
Start with [your first box](../first-box.mdx) if you have not run a command yet.
|
|
9
|
+
Commands run on the controller unless a block is labeled **HOST**.
|
|
10
|
+
|
|
11
|
+
## Develop
|
|
12
|
+
|
|
13
|
+
- [Project setup and templates](./project-setup.mdx): upload a project and reuse dependencies.
|
|
14
|
+
- [Docker](./docker.mdx): build images and manage guest disk use.
|
|
15
|
+
- [File transfer](./files.mdx): copy individual files or directory trees.
|
|
16
|
+
- [Desktop and private services](./desktop-services.mdx): open a browser, desktop or local tunnel.
|
|
17
|
+
|
|
18
|
+
## Automate
|
|
19
|
+
|
|
20
|
+
- [Background jobs](./background-jobs.mdx): detach, inspect logs and cancel.
|
|
21
|
+
- [Lifetime and persistent services](./long-running-services.mdx): set deadlines and restart services after resume.
|
|
22
|
+
- [Scripting and CI](./scripting-ci.mdx): parse output and handle failures.
|
|
23
|
+
- [Disposable CI jobs — unreleased 0.7.0](./ci-jobs.mdx): run and clean up one temporary build.
|
|
24
|
+
|
|
25
|
+
## Operate
|
|
26
|
+
|
|
27
|
+
- [Snapshots and forks](./snapshots-forks.mdx): save disk state or make a same-host copy.
|
|
28
|
+
- [Backup and recovery](./backup-recovery.mdx): move a whole box to a compatible host.
|
|
29
|
+
- [Defaults and policies](./config-policy.mdx): set creation defaults and retention rules.
|
|
30
|
+
- [Upgrades](./upgrades.mdx): update the CLI, host worker and image separately.
|
|
31
|
+
- [Troubleshooting](./troubleshooting.mdx): start from a symptom.
|
|
@@ -11,7 +11,7 @@ terminal, and detached jobs do not extend that deadline automatically.
|
|
|
11
11
|
## Choose a lifetime and retention policy
|
|
12
12
|
|
|
13
13
|
```sh title="CONTROLLER: Create a persistent box"
|
|
14
|
-
box=$(raft new --location lab --ttl 3600)
|
|
14
|
+
box=$(raft new --location lab --ttl 3600 --persistent)
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
TTL is in seconds: 60 to 2,592,000 (30 days), with a creation default of 600.
|
|
@@ -24,13 +24,19 @@ Raft has no option to disable automatic expiry.
|
|
|
24
24
|
| `resume --ttl SECONDS` | Starts the saved box with a new deadline | Only possible while the box still exists |
|
|
25
25
|
| `destroy` | Deletes the box and snapshots | Deletes the box and snapshots |
|
|
26
26
|
|
|
27
|
-
Use `raft new --disposable` for temporary work
|
|
27
|
+
Use `raft new --disposable` for temporary work, or
|
|
28
|
+
`raft config set disposable true` to make it the default on this controller;
|
|
29
|
+
`raft new --persistent` then keeps one box. A fork is persistent unless
|
|
28
30
|
`--disposable` is explicitly supplied; recovered boxes are persistent.
|
|
29
31
|
Back up work before stopping a disposable box.
|
|
30
32
|
|
|
31
33
|
Persistent stopped boxes still occupy storage. Raft does not cap how many boxes
|
|
32
34
|
a host keeps. `raft prune --older-than 7` previews old stopped boxes;
|
|
33
|
-
`--yes` applies deletion.
|
|
35
|
+
`--yes` applies deletion. To delete them automatically, set a host retention
|
|
36
|
+
policy: with `raft policy lab --delete-stopped-after 48`, the host deletes any
|
|
37
|
+
box left stopped for 48 hours, and `raft list` shows each one's `deleteAt`.
|
|
38
|
+
Resuming a box resets its clock. See
|
|
39
|
+
[Defaults and host policies](./config-policy.mdx#delete-boxes-left-stopped).
|
|
34
40
|
|
|
35
41
|
## Read and reset the deadline
|
|
36
42
|
|