@microck/raft 0.0.0-stage → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/.github/assets/raft-logo.png +0 -0
  2. package/LICENSE +21 -0
  3. package/README.md +167 -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 +519 -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 +86 -0
  34. package/docs/guides/background-jobs.mdx +95 -0
  35. package/docs/guides/backup-recovery.mdx +111 -0
  36. package/docs/guides/desktop-services.mdx +90 -0
  37. package/docs/guides/docker.mdx +84 -0
  38. package/docs/guides/files.mdx +91 -0
  39. package/docs/guides/long-running-services.mdx +109 -0
  40. package/docs/guides/meta.json +18 -0
  41. package/docs/guides/project-setup.mdx +112 -0
  42. package/docs/guides/scripting-ci.mdx +133 -0
  43. package/docs/guides/snapshots-forks.mdx +107 -0
  44. package/docs/guides/troubleshooting.mdx +152 -0
  45. package/docs/guides/upgrades.mdx +113 -0
  46. package/docs/incus.example.json +3 -0
  47. package/docs/index.mdx +40 -0
  48. package/docs/meta.json +21 -0
  49. package/docs/operations.mdx +239 -0
  50. package/docs/publishing.md +48 -0
  51. package/docs/reference/capacity-limits.mdx +92 -0
  52. package/docs/reference/cli.md +288 -0
  53. package/docs/reference/configuration.mdx +77 -0
  54. package/docs/reference/meta.json +10 -0
  55. package/docs/releases/0.2.0.md +22 -0
  56. package/docs/releases/0.2.1.md +14 -0
  57. package/docs/requirements.mdx +47 -0
  58. package/docs/reviews/code-review-2026-10-06.md +168 -0
  59. package/docs/site-development.md +80 -0
  60. package/docs/verification.mdx +237 -0
  61. package/images/incus/build.sh +57 -0
  62. package/images/incus/desktop.sh +21 -0
  63. package/images/incus/raft-desktop.service +7 -0
  64. package/package.json +46 -4
  65. package/raft.py +961 -0
  66. package/raft_files.py +134 -0
  67. package/skills/raft-cli/SKILL.md +117 -0
@@ -0,0 +1,95 @@
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.
93
+
94
+ For deadline inspection and an enabled systemd service that starts again after
95
+ resume or fork, see [Lifetime and persistent services](./long-running-services.mdx).
@@ -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,90 @@
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` requests startup of `raft-desktop.service` inside the container if
70
+ needed and opens a tunnel to local port `6080`. The service waits for Xvfb before
71
+ launching its desktop clients. The CLI does not poll noVNC readiness; if the first
72
+ browser request fails during startup, retry after the service initializes.
73
+
74
+ Open the client in your browser:
75
+
76
+ ```text
77
+ http://127.0.0.1:6080/vnc.html
78
+ ```
79
+
80
+ <Callout type="info" title="Browser URL requirement">
81
+ 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.
82
+ </Callout>
83
+
84
+ ### Desktop characteristics
85
+
86
+ - **Display environment**: Virtual X display `:99` at 1280x720 resolution with 24-bit color.
87
+ - **User session**: Openbox runs under the `developer` user account.
88
+ - **Clipboard support**: The noVNC sidebar provides a clipboard drawer for text exchange.
89
+ - **Tunnel lifecycle**: Pressing `Ctrl+C` closes the local tunnel. The desktop service keeps running until the workspace stops or expires.
90
+ - **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,109 @@
1
+ ---
2
+ title: Lifetime and persistent services
3
+ description: Inspect deadlines, manage disposable and persistent boxes, and restart applications after resume.
4
+ ---
5
+
6
+ # Lifetime and persistent services
7
+
8
+ A box's lifetime controls when the host stops or deletes it. Activity, an open
9
+ terminal, and detached jobs do not extend that deadline automatically.
10
+
11
+ ## Choose a lifetime and retention policy
12
+
13
+ ```sh title="CONTROLLER: Create a persistent box"
14
+ box=$(raft new --location lab --ttl 3600)
15
+ ```
16
+
17
+ TTL is in seconds: 60 to 2,592,000 (30 days), with a creation default of 600.
18
+ The host sets the deadline after launch completes, using its own clock.
19
+ Raft has no option to disable automatic expiry.
20
+
21
+ | Operation | Persistent box (default) | Disposable box |
22
+ | --- | --- | --- |
23
+ | `stop` or TTL expiry | Stops processes; keeps disk and snapshots | Deletes the box and snapshots |
24
+ | `resume --ttl SECONDS` | Starts the saved box with a new deadline | Only possible while the box still exists |
25
+ | `destroy` | Deletes the box and snapshots | Deletes the box and snapshots |
26
+
27
+ Use `raft new --disposable` for temporary work. A fork is persistent unless
28
+ `--disposable` is explicitly supplied; recovered boxes are persistent.
29
+ Back up work before stopping a disposable box.
30
+
31
+ Persistent stopped boxes still occupy a saved slot and storage. Each host allows
32
+ four saved boxes. `raft prune --older-than 7` previews old stopped boxes;
33
+ `--yes` applies deletion. There is no automatic persistent-box retention cleanup.
34
+
35
+ ## Read and reset the deadline
36
+
37
+ Select a running handle from `raft list`. These examples use controller `jq`:
38
+
39
+ ```sh title="CONTROLLER: Inspect and extend a running box"
40
+ raft list
41
+ box="lab:rf-a1b2c3d4e5f60718" # Replace with your actual handle
42
+ raft info "$box" | jq '{status, expires: .config["user.raft.expires"]}'
43
+ raft extend "$box" --ttl 7200
44
+ raft info "$box" | jq '{status, expires: .config["user.raft.expires"]}'
45
+ ```
46
+
47
+ `user.raft.expires` is a Unix timestamp encoded as a string, not a remaining
48
+ duration. `raft list` exposes it as `stopAt`; it may be empty on stopped boxes.
49
+ `extend` sets `now + TTL`, rather than adding time to the old deadline.
50
+ It requires a running box. Start a stopped box with `raft resume "$box" --ttl 7200`.
51
+
52
+ The host timer checks every 15 seconds. Graceful shutdown, a host outage, or a
53
+ long transaction holding the lifecycle lock can delay expiry. `raft gc --location lab`
54
+ runs an immediate deadline check. A deadline is not a guarantee that a job will
55
+ finish before shutdown; extend before starting work that needs more time.
56
+
57
+ ## Restart a service after resume
58
+
59
+ One-off detached commands do not survive a stop. To start an application again
60
+ after resume or fork, save a systemd unit on disk and enable it. This starts a new
61
+ process; it does not recover process memory or an interrupted request.
62
+
63
+ The following example serves `/workspace` as the guest `developer` user. Use a
64
+ persistent running box and ensure files the application needs are readable by
65
+ that user. For an actual application, replace `ExecStart` and the working directory.
66
+
67
+ ```sh title="CONTROLLER: Install and enable an example service"
68
+ raft exec "$box" -- bash -lc 'cat > /etc/systemd/system/workspace-web.service <<"UNIT"
69
+ [Unit]
70
+ Description=Workspace HTTP example
71
+ After=network.target
72
+
73
+ [Service]
74
+ User=developer
75
+ WorkingDirectory=/workspace
76
+ ExecStart=/usr/bin/python3 -m http.server 8080 --bind 0.0.0.0
77
+ Restart=on-failure
78
+ RestartSec=2
79
+
80
+ [Install]
81
+ WantedBy=multi-user.target
82
+ UNIT
83
+ systemctl daemon-reload
84
+ systemctl enable --now workspace-web.service'
85
+ raft exec "$box" -- systemctl status workspace-web.service --no-pager
86
+ raft exec "$box" -- journalctl -u workspace-web.service -n 30 --no-pager
87
+ ```
88
+
89
+ Open `raft forward "$box" --remote 8080 --local 8080` and visit
90
+ `http://127.0.0.1:8080`. The tunnel runs in the foreground; close it with `Ctrl+C`.
91
+
92
+ ```sh title="CONTROLLER: Check service startup after resume"
93
+ raft stop "$box"
94
+ raft resume "$box" --ttl 3600
95
+ raft exec "$box" -- systemctl is-active workspace-web.service
96
+ ```
97
+
98
+ Reopen the tunnel after resume. A fork or snapshot template copies the enabled
99
+ unit too, so its new box starts its own service. Application secrets on disk are
100
+ also copied; see [Project setup and templates](./project-setup.mdx).
101
+
102
+ To disable the example, run:
103
+
104
+ ```sh
105
+ raft exec "$box" -- systemctl disable --now workspace-web.service
106
+ ```
107
+
108
+ See [Background jobs](./background-jobs.mdx) for one-off tasks and
109
+ [Desktop and private services](./desktop-services.mdx) for tunnel behavior.
@@ -0,0 +1,18 @@
1
+ {
2
+ "title": "Guides",
3
+ "collapsible": true,
4
+ "defaultOpen": false,
5
+ "pages": [
6
+ "project-setup",
7
+ "docker",
8
+ "files",
9
+ "background-jobs",
10
+ "long-running-services",
11
+ "scripting-ci",
12
+ "desktop-services",
13
+ "snapshots-forks",
14
+ "backup-recovery",
15
+ "upgrades",
16
+ "troubleshooting"
17
+ ]
18
+ }