@microck/raft 0.0.0-stage → 0.2.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.
Files changed (62) hide show
  1. package/.github/assets/raft-logo.png +0 -0
  2. package/LICENSE +21 -0
  3. package/README.md +162 -2
  4. package/deploy/benchmark-boot.py +150 -0
  5. package/deploy/build-image.py +95 -0
  6. package/deploy/deploy.py +176 -0
  7. package/deploy/incus-network.conf +4 -0
  8. package/deploy/install-incus.sh +22 -0
  9. package/deploy/raft-expire.py +62 -0
  10. package/deploy/raft-expire.service +8 -0
  11. package/deploy/raft-expire.timer +8 -0
  12. package/deploy/raft-network.service +12 -0
  13. package/deploy/raft-network.sh +40 -0
  14. package/deploy/verify-controls.py +241 -0
  15. package/deploy/verify-features.py +461 -0
  16. package/deploy/verify-fresh-host.py +301 -0
  17. package/deploy/verify-image-tests.py +139 -0
  18. package/deploy/verify-image.py +95 -0
  19. package/deploy/verify-limits.py +110 -0
  20. package/deploy/verify-live.py +903 -0
  21. package/deploy/verify-release.py +132 -0
  22. package/deploy/verify-tools.py +105 -0
  23. package/docs/benchmarks/amd64-native-ci.json +137 -0
  24. package/docs/benchmarks/arm64-baseline-a.json +108 -0
  25. package/docs/benchmarks/arm64-baseline-b.json +108 -0
  26. package/docs/benchmarks/arm64-candidate-a.json +109 -0
  27. package/docs/benchmarks/arm64-desktop-local.json +22 -0
  28. package/docs/benchmarks/arm64-native-ci.json +137 -0
  29. package/docs/boat-capabilities.md +64 -0
  30. package/docs/boat-parity-design.md +26 -0
  31. package/docs/boot-performance.mdx +102 -0
  32. package/docs/contract.md +93 -0
  33. package/docs/first-box.mdx +82 -0
  34. package/docs/guides/background-jobs.mdx +92 -0
  35. package/docs/guides/backup-recovery.mdx +111 -0
  36. package/docs/guides/desktop-services.mdx +87 -0
  37. package/docs/guides/docker.mdx +84 -0
  38. package/docs/guides/files.mdx +91 -0
  39. package/docs/guides/meta.json +14 -0
  40. package/docs/guides/snapshots-forks.mdx +107 -0
  41. package/docs/guides/troubleshooting.mdx +127 -0
  42. package/docs/incus.example.json +3 -0
  43. package/docs/index.mdx +36 -0
  44. package/docs/meta.json +21 -0
  45. package/docs/operations.mdx +224 -0
  46. package/docs/publishing.md +48 -0
  47. package/docs/reference/capacity-limits.mdx +83 -0
  48. package/docs/reference/cli.md +279 -0
  49. package/docs/reference/configuration.mdx +77 -0
  50. package/docs/reference/meta.json +10 -0
  51. package/docs/releases/0.2.0.md +22 -0
  52. package/docs/requirements.mdx +47 -0
  53. package/docs/reviews/code-review-2026-10-06.md +168 -0
  54. package/docs/site-development.md +80 -0
  55. package/docs/verification.mdx +231 -0
  56. package/images/incus/build.sh +57 -0
  57. package/images/incus/desktop.sh +21 -0
  58. package/images/incus/raft-desktop.service +7 -0
  59. package/package.json +46 -4
  60. package/raft.py +955 -0
  61. package/raft_files.py +134 -0
  62. package/skills/raft-cli/SKILL.md +117 -0
@@ -0,0 +1,92 @@
1
+ ---
2
+ title: Background jobs
3
+ description: Run detached commands with systemd logging and process management.
4
+ ---
5
+
6
+ # Background jobs
7
+
8
+ Raft runs detached background commands under the guest systemd init system using transient service units.
9
+
10
+ ## Target workspace handle
11
+
12
+ Commands in this guide target a running workspace. Select one from `raft list` (see [First working box](../first-box.mdx)):
13
+
14
+ ```sh title="CONTROLLER: Select running workspace"
15
+ raft list
16
+ box="lab:rf-a1b2c3d4e5f60718" # Replace with your actual workspace handle
17
+ ```
18
+
19
+ ## Launching detached commands
20
+
21
+ Start a background command by passing `--detach` to `raft exec`, delimiting the payload with `--`:
22
+
23
+ ```sh title="CONTROLLER: Start a detached background job"
24
+ job=$(raft exec "$box" --detach -- bash -lc '
25
+ echo "Task started at $(date)"
26
+ sleep 60
27
+ echo "Task finished at $(date)"
28
+ ')
29
+ echo "Started background job: $job"
30
+ ```
31
+
32
+ The command returns a 32-hex systemd unit name prefixed with `rfcmd-` (such as `rfcmd-a1b2c3d4e5f60718293a4b5c6d7e8f90`).
33
+
34
+ ## Checking job status
35
+
36
+ Use `raft status` to query guest systemd unit properties:
37
+
38
+ ```sh title="CONTROLLER: Inspect background job state"
39
+ raft status "$box" "$job"
40
+ ```
41
+
42
+ Active jobs report running substate:
43
+ ```text
44
+ ActiveState=active
45
+ SubState=running
46
+ ExecMainStatus=0
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
61
+
62
+ Retrieve output recorded in the guest systemd journal:
63
+
64
+ ```sh title="CONTROLLER: Retrieve job log output"
65
+ raft logs "$box" "$job"
66
+ ```
67
+
68
+ The command runs `journalctl --no-pager -o cat -u <job>` inside the container. It prints recorded logs without a pager or stream follow mode.
69
+
70
+ ## Cancelling jobs
71
+
72
+ Terminate a running detached command:
73
+
74
+ ```sh title="CONTROLLER: Terminate background job"
75
+ raft cancel "$box" "$job"
76
+ ```
77
+
78
+ `raft cancel` executes `systemctl stop <job>`, terminating processes in the job's systemd control group.
79
+
80
+ ## Extending workspace lifetime
81
+
82
+ <Callout type="warn" title="No process checkpointing">
83
+ Stopping a workspace with `raft stop` or allowing its TTL to expire terminates all running background jobs. Raft does not checkpoint running process memory.
84
+ </Callout>
85
+
86
+ Extend the workspace lifetime before it expires:
87
+
88
+ ```sh title="CONTROLLER: Reset workspace expiration"
89
+ raft extend "$box" --ttl 1800
90
+ ```
91
+
92
+ `raft extend` resets the container expiration deadline to `now + TTL` seconds using the host clock. It does not add time to the existing deadline.
@@ -0,0 +1,111 @@
1
+ ---
2
+ title: Backup and recovery
3
+ description: Export portable workspace archives and restore them across hosts.
4
+ ---
5
+
6
+ # Backup and recovery
7
+
8
+ Raft exports portable workspace archives and restores them onto compatible hosts for disaster recovery, migration, or offline archiving.
9
+
10
+ ## Target workspace handle
11
+
12
+ Commands in this guide require a stopped workspace:
13
+
14
+ ```sh title="CONTROLLER: Select and stop target workspace"
15
+ raft list
16
+ box="lab:rf-a1b2c3d4e5f60718" # Replace with your actual workspace handle
17
+ raft stop "$box"
18
+ ```
19
+
20
+ ## Exporting a backup
21
+
22
+ Specify a destination archive path on the controller:
23
+
24
+ ```sh title="CONTROLLER: Export stopped workspace to compressed archive"
25
+ raft backup "$box" ./my-workspace-backup.tar.gz
26
+ ```
27
+
28
+ `raft backup` exports container metadata, root filesystem, and snapshots into a gzip-compressed archive.
29
+
30
+ ### Security and storage requirements
31
+
32
+ <Callout type="warn" title="Archives contain sensitive data">
33
+ Backup archives contain the entire guest filesystem, including credentials, SSH keys, shell history, and private files. Native Incus archive configuration is not sanitized. Import only trusted archives created by Raft, and store backups on encrypted private storage off the source host for disaster recovery.
34
+ </Callout>
35
+
36
+ Raft enforces safeguards during export:
37
+
38
+ - **Private permissions**: Destination mode must be `0600`. Group and world permissions must be zero before export begins.
39
+ - **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
+ - **No overwriting**: The destination file must not already exist.
41
+ - **Lifecycle lock**: Export holds `/run/lock/raft-incus.lock`. Expiry checks for other containers on the host are delayed while the lock is held.
42
+
43
+ ## Recovering an archive
44
+
45
+ Import the archive onto the target location:
46
+
47
+ ```sh title="CONTROLLER: Import workspace backup onto target host"
48
+ recovered=$(raft recover ./my-workspace-backup.tar.gz --location lab)
49
+ echo "Recovered workspace: $recovered"
50
+ ```
51
+
52
+ ### Recovery process details
53
+
54
+ - **Architecture compatibility**: Target host must match the native CPU architecture (ARM64 to ARM64, AMD64 to AMD64). Cross-architecture recovery is not supported.
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
+ - **Enforced admission limit**: The destination location must hold fewer than four boxes.
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
+
60
+ Start and verify the recovered workspace:
61
+
62
+ ```sh title="CONTROLLER: Start and verify recovered workspace"
63
+ raft resume "$recovered" --ttl 600
64
+ raft exec "$recovered" -- ls -la /workspace
65
+ ```
66
+
67
+ ## Cross-host migration workflow
68
+
69
+ Follow these steps to migrate a workspace to a second host:
70
+
71
+ ### 1. Select and stop source workspace
72
+
73
+ ```sh title="CONTROLLER"
74
+ raft list
75
+ source_box="lab:rf-a1b2c3d4e5f60718"
76
+ raft stop "$source_box"
77
+ ```
78
+
79
+ ### 2. Export archive to local controller storage
80
+
81
+ ```sh title="CONTROLLER"
82
+ raft backup "$source_box" ./migration.tar.gz
83
+ ```
84
+
85
+ ### 3. Import archive onto destination host
86
+
87
+ ```sh title="CONTROLLER"
88
+ target_box=$(raft recover ./migration.tar.gz --location second-host)
89
+ ```
90
+
91
+ ### 4. Resume workspace on destination host
92
+
93
+ ```sh title="CONTROLLER"
94
+ raft resume "$target_box" --ttl 1800
95
+ ```
96
+
97
+ ### 5. Verify application files and operations
98
+
99
+ ```sh title="CONTROLLER"
100
+ raft exec "$target_box" -- ls -la /workspace
101
+ ```
102
+
103
+ After verifying the recovered workspace, optionally destroy the source container:
104
+
105
+ ```sh title="CONTROLLER: Optional: Destroy original container"
106
+ raft destroy "$source_box"
107
+ ```
108
+
109
+ <Callout type="warn" title="Preserve backup archives">
110
+ Do not delete your local backup archive until you confirm that the destination workspace operates normally.
111
+ </Callout>
@@ -0,0 +1,87 @@
1
+ ---
2
+ title: Desktop and private services
3
+ description: Forward network ports and connect to an on-demand graphical desktop.
4
+ ---
5
+
6
+ # Desktop and private services
7
+
8
+ Forward network ports and connect to an on-demand graphical desktop over authenticated host SSH tunnels bound to controller loopback.
9
+
10
+ ## Target workspace handle
11
+
12
+ Commands in this guide target a running workspace. Select one from `raft list` (see [First working box](../first-box.mdx)):
13
+
14
+ ```sh title="CONTROLLER: Select running workspace"
15
+ raft list
16
+ box="lab:rf-a1b2c3d4e5f60718" # Replace with your actual workspace handle
17
+ ```
18
+
19
+ ## Forwarding network ports
20
+
21
+ Create a secure tunnel between your local controller and a workspace service:
22
+
23
+ ```text
24
+ raft forward "$box" --remote <REMOTE_PORT> --local <LOCAL_PORT>
25
+ ```
26
+
27
+ ### End-to-end example: Running a web service
28
+
29
+ Start a detached HTTP server inside the workspace:
30
+
31
+ ```sh title="CONTROLLER: Start detached HTTP server inside workspace"
32
+ job=$(raft exec "$box" --detach -- python3 -m http.server 8080 --bind 0.0.0.0)
33
+ echo "Started web service job: $job"
34
+ ```
35
+
36
+ Open the forwarding tunnel:
37
+
38
+ ```sh title="CONTROLLER: Open port forwarding tunnel"
39
+ raft forward "$box" --remote 8080 --local 8080
40
+ ```
41
+
42
+ `raft forward` runs in the foreground. Open `http://127.0.0.1:8080` in your browser or run `curl http://127.0.0.1:8080` in another terminal.
43
+
44
+ When finished:
45
+ 1. Press `Ctrl+C` to close the tunnel.
46
+ 2. Cancel the background job:
47
+ ```sh title="CONTROLLER: Stop the background server job"
48
+ raft cancel "$box" "$job"
49
+ ```
50
+
51
+ ### Binding requirement
52
+
53
+ <Callout type="warn" title="Bind to all interfaces or guest IP">
54
+ Services inside the container must bind to all interfaces (`0.0.0.0`) or to the container's private `eth0` IP address. A service listening strictly on container loopback (`127.0.0.1`) cannot receive traffic routed across the container bridge.
55
+ </Callout>
56
+
57
+ The controller listener binds strictly to `127.0.0.1` on your local workstation, without exposing ports to your local network.
58
+
59
+ ## On-demand desktop environment
60
+
61
+ The development image includes a graphical desktop stack using Xvfb (display `:99`), Openbox, x11vnc, and noVNC.
62
+
63
+ Start the desktop and establish a private tunnel:
64
+
65
+ ```sh title="CONTROLLER: Launch desktop and establish private tunnel"
66
+ raft desktop "$box" --local 6080
67
+ ```
68
+
69
+ `raft desktop` starts `raft-desktop.service` inside the container if needed, waits for Xvfb and VNC readiness, and opens a tunnel to local port `6080`.
70
+
71
+ Open the client in your browser:
72
+
73
+ ```text
74
+ http://127.0.0.1:6080/vnc.html
75
+ ```
76
+
77
+ <Callout type="info" title="Browser URL requirement">
78
+ Include `/vnc.html` in the URL. The root path `/` displays an index directory; `/vnc.html` loads the desktop canvas with mouse, keyboard, and clipboard support.
79
+ </Callout>
80
+
81
+ ### Desktop characteristics
82
+
83
+ - **Display environment**: Virtual X display `:99` at 1280x720 resolution with 24-bit color.
84
+ - **User session**: Openbox runs under the `developer` user account.
85
+ - **Clipboard support**: The noVNC sidebar provides a clipboard drawer for text exchange.
86
+ - **Tunnel lifecycle**: Pressing `Ctrl+C` closes the local tunnel. The desktop service keeps running until the workspace stops or expires.
87
+ - **Reconnecting**: Running `raft desktop "$box" --local 6080` reconnects without interrupting open windows.
@@ -0,0 +1,84 @@
1
+ ---
2
+ title: Docker in workspaces
3
+ description: Run Docker daemons and build container images inside unprivileged Raft workspaces.
4
+ ---
5
+
6
+ # Docker in workspaces
7
+
8
+ Raft development images include Docker daemon and CLI tooling running directly in the unprivileged container without mounting the host Docker socket.
9
+
10
+ ## Target workspace handle
11
+
12
+ Commands in this guide target a running workspace. Select one from `raft list` (see [First working box](../first-box.mdx)):
13
+
14
+ ```sh title="CONTROLLER: Select running workspace"
15
+ raft list
16
+ box="lab:rf-a1b2c3d4e5f60718" # Replace with your actual workspace handle
17
+ ```
18
+
19
+ ## Verifying Docker status
20
+
21
+ Verify that the guest Docker daemon is initialized and responsive:
22
+
23
+ ```sh title="CONTROLLER: Check Docker version and daemon status"
24
+ raft exec "$box" -- docker version
25
+ raft exec "$box" -- docker info
26
+ ```
27
+
28
+ <Callout type="info" title="Daemon readiness verification">
29
+ `docker version` and `docker info` query guest `dockerd`. Running `docker --version` checks only the client binary without testing daemon health.
30
+ </Callout>
31
+
32
+ ## Running containers
33
+
34
+ Run containers inside your workspace:
35
+
36
+ ```sh title="CONTROLLER: Run an Alpine container"
37
+ raft exec "$box" -- docker run --rm alpine uname -a
38
+ ```
39
+
40
+ Start background services inside the workspace:
41
+
42
+ ```sh title="CONTROLLER: Start a background Redis container"
43
+ raft exec "$box" -- docker run -d --name cache -p 6379:6379 redis:alpine
44
+ raft exec "$box" -- docker ps
45
+ ```
46
+
47
+ ## Building container images
48
+
49
+ Build container images within the workspace:
50
+
51
+ ```sh title="CONTROLLER: Create Dockerfile and build image"
52
+ raft exec "$box" -- bash -lc '
53
+ mkdir -p /workspace/app
54
+ cat << "EOF" > /workspace/app/Dockerfile
55
+ FROM alpine:latest
56
+ RUN echo "hello from workspace container" > /message.txt
57
+ CMD ["cat", "/message.txt"]
58
+ EOF
59
+ cd /workspace/app
60
+ docker build -t my-app .
61
+ docker run --rm my-app
62
+ '
63
+ ```
64
+
65
+ ## Managing disk space
66
+
67
+ Nested container layers and volumes reside on the shared 60 GiB `raft-data` Btrfs storage pool.
68
+
69
+ Prune dangling images without deleting active container data:
70
+
71
+ ```sh title="CONTROLLER: Prune dangling images"
72
+ raft exec "$box" -- docker image prune -f
73
+ ```
74
+
75
+ <Callout type="warn" title="Data loss risk with system prune">
76
+ Running `docker system prune -af --volumes` removes all stopped containers and unused volumes inside the workspace. Run full pruning only when you intend to discard local container state and caches.
77
+ </Callout>
78
+
79
+ ## Isolation and runtime limits
80
+
81
+ - **Daemon confinement**: The Docker daemon runs as a guest systemd unit using `/var/run/docker.sock`. The host Docker daemon is never mounted.
82
+ - **Kernel sharing**: Workspaces share the host Linux kernel without host kernel module or KVM access.
83
+ - **Syscall interception**: Incus sets `security.nesting=true`, `security.syscalls.intercept.mknod=true`, and `security.syscalls.intercept.setxattr=true` to manage image layers safely in unprivileged user namespaces.
84
+ - **Shutdown behavior**: Stopping a workspace with `raft stop` stops guest processes and nested containers.
@@ -0,0 +1,91 @@
1
+ ---
2
+ title: File transfer
3
+ description: Transfer files between your controller machine and running Raft workspaces.
4
+ ---
5
+
6
+ # File transfer
7
+
8
+ Upload and download individual files between your local controller and running workspaces over verified host SSH and Incus instance APIs.
9
+
10
+ ## Target workspace handle
11
+
12
+ Commands in this guide target a running workspace. Select one from `raft list` (see [First working box](../first-box.mdx)):
13
+
14
+ ```sh title="CONTROLLER: Select running workspace"
15
+ raft list
16
+ box="lab:rf-a1b2c3d4e5f60718" # Replace with your actual workspace handle
17
+ ```
18
+
19
+ ## Uploading files
20
+
21
+ Transfer a file from your controller to an absolute path inside the workspace:
22
+
23
+ ```sh title="CONTROLLER: Create fixture and upload to workspace"
24
+ # Create local sample file on controller
25
+ echo "Hello from controller" > ./local-file.txt
26
+
27
+ # Upload to workspace
28
+ raft upload "$box" ./local-file.txt /workspace/target-file.txt
29
+ ```
30
+
31
+ Existing destination files are overwritten. Create parent directories before uploading if needed:
32
+
33
+ ```sh title="CONTROLLER: Create parent directory and upload"
34
+ echo '{"mode": "debug"}' > ./settings.json
35
+ raft exec "$box" -- mkdir -p /workspace/configs
36
+ raft upload "$box" ./settings.json /workspace/configs/settings.json
37
+ ```
38
+
39
+ <Callout type="info" title="Absolute guest paths required">
40
+ Guest file paths must be absolute paths beginning with `/`. Relative paths inside the container are rejected by the CLI parser.
41
+ </Callout>
42
+
43
+ ## Downloading files
44
+
45
+ Download a file from a workspace to your controller:
46
+
47
+ ```sh title="CONTROLLER: Download a file from workspace"
48
+ raft download "$box" /workspace/target-file.txt ./downloaded-file.txt
49
+ ```
50
+
51
+ The destination path on the controller must specify the target filename.
52
+
53
+ ## Paths with spaces
54
+
55
+ Wrap paths in quotes when transferring files with spaces:
56
+
57
+ ```sh title="CONTROLLER: Transfer files with spaces in paths"
58
+ mkdir -p "./my documents"
59
+ echo "col1,col2" > "./my documents/data file.csv"
60
+
61
+ raft exec "$box" -- mkdir -p "/workspace/my documents"
62
+ raft upload "$box" "./my documents/data file.csv" "/workspace/my documents/data file.csv"
63
+ raft download "$box" "/workspace/my documents/data file.csv" "./downloaded-data.csv"
64
+ ```
65
+
66
+ ## Transferring directories
67
+
68
+ Use `--recursive` to copy a tree into a new directory:
69
+
70
+ ```sh
71
+ raft upload "$box" ./my-project /workspace/my-project --recursive
72
+ raft download "$box" /workspace/my-project ./project-copy --recursive
73
+ ```
74
+
75
+ The destination must not exist, and its parent must exist. Raft publishes the directory after the complete transfer; it does not merge or overwrite trees.
76
+
77
+ | Preserved | Rejected |
78
+ | --- | --- |
79
+ | File contents and executable bits | Existing destination directories or symlinks |
80
+ | Empty directories and internal hardlinks | Paths or links that escape the tree |
81
+ | Relative symlinks within the tree | Devices, FIFOs and other special files |
82
+
83
+ Recursive transfers require a running box with Python 3.11.8+ and temporary disk space on the controller. Individual-file transfers also work while a box is stopped.
84
+
85
+ ## File ownership
86
+
87
+ Uploaded files are owned by `root` in the guest. Update ownership to `developer` when needed:
88
+
89
+ ```sh title="CONTROLLER: Change file ownership inside container"
90
+ raft exec "$box" -- chown -R developer:developer /workspace/my-project
91
+ ```
@@ -0,0 +1,14 @@
1
+ {
2
+ "title": "Guides",
3
+ "collapsible": true,
4
+ "defaultOpen": false,
5
+ "pages": [
6
+ "docker",
7
+ "files",
8
+ "background-jobs",
9
+ "desktop-services",
10
+ "snapshots-forks",
11
+ "backup-recovery",
12
+ "troubleshooting"
13
+ ]
14
+ }
@@ -0,0 +1,107 @@
1
+ ---
2
+ title: Snapshots and forks
3
+ description: Capture point-in-time filesystem snapshots and fork independent workspace clones.
4
+ ---
5
+
6
+ # Snapshots and forks
7
+
8
+ Raft captures filesystem snapshots, rolls back changes, and clones workspaces on the same host.
9
+
10
+ ## Target workspace handle
11
+
12
+ Commands in this guide require a stopped workspace:
13
+
14
+ ```sh title="CONTROLLER: Select and stop target workspace"
15
+ raft list
16
+ box="lab:rf-a1b2c3d4e5f60718" # Replace with your actual workspace handle
17
+ raft stop "$box"
18
+ ```
19
+
20
+ ## Stopped-source requirement
21
+
22
+ <Callout type="warn" title="Workspace must be stopped">
23
+ Creating a snapshot, restoring from a snapshot, or forking requires a stopped container. Operations run under host lock `/run/lock/raft-incus.lock` to prevent concurrent disk modification.
24
+ </Callout>
25
+
26
+ ## Creating snapshots
27
+
28
+ Take a point-in-time snapshot of the stopped workspace filesystem:
29
+
30
+ ```sh title="CONTROLLER: Create a named snapshot"
31
+ raft snapshot "$box" pre-upgrade
32
+ ```
33
+
34
+ Snapshot names must use letters, digits, underscores, or hyphens up to 64 characters.
35
+
36
+ ## Listing snapshots
37
+
38
+ View existing snapshots for a workspace:
39
+
40
+ ```sh title="CONTROLLER: List existing snapshots"
41
+ raft snapshots "$box"
42
+ ```
43
+
44
+ The command prints snapshot names and creation timestamps from Incus metadata.
45
+
46
+ ## Restoring from a snapshot
47
+
48
+ Roll back a stopped workspace to a previously recorded snapshot:
49
+
50
+ ```sh title="CONTROLLER: Restore snapshot"
51
+ raft restore "$box" pre-upgrade
52
+ ```
53
+
54
+ <Callout type="info" title="Configuration preservation during restore">
55
+ `raft restore` executes Incus snapshot restore with `--diskonly`. It rolls back filesystem blocks while preserving container configuration, CPU and memory limits, and the `eth0` network MAC address.
56
+ </Callout>
57
+
58
+ Resume the workspace with a renewed lifetime:
59
+
60
+ ```sh title="CONTROLLER: Resume restored workspace"
61
+ raft resume "$box" --ttl 600
62
+ ```
63
+
64
+ ## Forking a workspace
65
+
66
+ Forking creates an independent clone on the same host:
67
+
68
+ ```sh title="CONTROLLER: Fork workspace into a new clone"
69
+ copy=$(raft fork "$box" --ttl 600)
70
+ echo "Created fork: $copy"
71
+ ```
72
+
73
+ ### Characteristics of forked workspaces
74
+
75
+ - **Instance-only copy**: Uses Incus `--instance-only`. Filesystem data, installed packages, and `/workspace` files are copied; source snapshots are not inherited.
76
+ - **Immediate start**: `raft fork` launches the new container immediately with the specified TTL while the source remains stopped.
77
+ - **Fresh network identity**: The fork receives a new container name, a fresh MAC address on `rfbr0`, and an independent IP address.
78
+ - **Capacity accounting**: The fork counts toward the location's four-saved-box limit and uses storage from the shared 60 GiB Btrfs pool.
79
+
80
+ Destroy the clone when finished to release capacity:
81
+
82
+ ```sh title="CONTROLLER: Destroy forked workspace"
83
+ raft destroy "$copy"
84
+ ```
85
+
86
+ ## Starting from a prepared snapshot
87
+
88
+ ```sh
89
+ copy=$(raft new --from "$box/pre-upgrade" --ttl 600)
90
+ ```
91
+
92
+ The source box can be running because Raft copies the saved snapshot. The new box stays on the same host, uses normal creation sizing defaults and has a fresh network identity. Filesystem secrets are copied too.
93
+
94
+ Remove a snapshot you no longer need:
95
+
96
+ ```sh
97
+ raft snapshot-delete "$box" pre-upgrade
98
+ ```
99
+
100
+ ## Choosing clone resources
101
+
102
+ ```sh
103
+ copy=$(raft fork "$box" --ttl 600 --cpu 2 --memory 4GiB)
104
+ raft resume "$box" --ttl 600 --memory 2GiB
105
+ ```
106
+
107
+ Omitted sizing values keep the source limits. Forks are persistent by default; add `--disposable` to delete a clone when it stops or expires.