Repository navigation
Add credential-free Sprite base builder #9618
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
291a10f
1d28748
23d2096
dc935e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| # Fly Sprites | ||
|
|
||
| Fly Sprites can host a persistent remote cmux session on a normal ext4 | ||
| filesystem. The base checkpoint contains the pinned `cmux` npm distribution | ||
| and a Sprite service, but deliberately excludes every daemon identity, | ||
| invitation, client key, and provider credential. | ||
|
|
||
| ## Build the base checkpoint | ||
|
|
||
| Install the Sprite CLI, source an owner-only token file, and run: | ||
|
|
||
| ```sh | ||
| set -a | ||
| source ~/.secrets/fly-sprites.env | ||
| set +a | ||
| cmux-tui/scripts/build-sprite-base.sh --org manaflow | ||
| ``` | ||
|
|
||
| The script prints the Sprite name and checkpoint id. It leaves the `cmux-tui` | ||
| service stopped and `/home/sprite/.local/share/cmux-sprite/remote` absent. | ||
| Starting the service after a new Sprite is provisioned generates a unique | ||
| daemon identity. | ||
|
|
||
| Checkpoints currently restore one Sprite. Fly's cross-Sprite drive forking is | ||
| in beta and is not exposed by the public REST API. Until that API ships, this | ||
| checkpoint is a verified base artifact rather than a generally available | ||
| create-time template. | ||
|
|
||
| ## Authentication boundary | ||
|
|
||
| There are two independent trust planes: | ||
|
|
||
| 1. The cmux backend owns `SPRITE_TOKEN` and uses it for Sprite lifecycle, | ||
| service, filesystem, and exec APIs. The token never enters a Sprite, native | ||
| client, command argument, checkpoint, or database row. | ||
| 2. Native clients connect directly through the public Sprite HTTPS edge, but | ||
| cmux authenticates the daemon key and device key inside an end-to-end Noise | ||
| session. The public URL is an untrusted carrier, not an authorization | ||
| boundary. | ||
|
|
||
| Do not put a static `--ws-token` in the checkpoint. Do not checkpoint | ||
| `/home/sprite/.local/share/cmux-sprite/remote`; clones would share the daemon | ||
| private key and enrolled-device database. | ||
|
|
||
| ## Provision and enroll one client | ||
|
|
||
| Start the service after provisioning: | ||
|
|
||
| ```sh | ||
| sprite exec -o <org> -s <sprite> -- sprite-env services start cmux-tui | ||
| ``` | ||
|
|
||
| Fetch the Sprite URL through the authenticated management API. Create a | ||
| single-device, short-lived invitation inside the Sprite: | ||
|
|
||
| ```sh | ||
| cmux enroll create \ | ||
| --session sprite \ | ||
| --state-dir /home/sprite/.local/share/cmux-sprite/remote \ | ||
| --advertise wss://<sprite-host>/v1/link \ | ||
| --ttl 300 \ | ||
| --json | ||
| ``` | ||
|
|
||
| The backend returns the invitation once to the already authenticated cmux | ||
| user. The native client stores it in an owner-only file, claims it with its | ||
| persistent device key, and waits: | ||
|
|
||
| ```sh | ||
| cmux connect \ | ||
| --invite-file ~/.config/cmux/sprite.invite \ | ||
| --device-name <device-name> | ||
| ``` | ||
|
|
||
| The backend then runs `cmux enroll pending` through authenticated Sprite exec. | ||
| It approves only the invitation id it issued for that user, VM, and expiry. | ||
| Never approve every pending request, and never identify a claimant only by its | ||
| display name. | ||
|
|
||
| After approval, the client reconnects with its device key. The invitation is | ||
| single-device and expires after five minutes. Revocation uses `cmux enroll | ||
| revoke`; it closes live sessions and rejects the device in the future. | ||
|
|
||
| For a team-owned Sprite, authorize invitation creation and approval against | ||
| the exact team and VM row before any provider call. Record the issued | ||
| invitation id, user id, VM id, expiry, and final device fingerprint. Store no | ||
| invitation URI or Sprite token. | ||
|
|
||
| ## Why the URL is public | ||
|
|
||
| The current native WebSocket client does not attach a Fly organization token. | ||
| Keeping the Sprite URL private would require a cmux backend relay that adds the | ||
| provider credential. A public URL avoids that relay and preserves direct | ||
| interactive latency. Unauthenticated callers can reach the WebSocket edge but | ||
| cannot complete cmux's Noise handshake or access mux data. | ||
|
|
||
| If direct public carriers are unacceptable, implement a machine-provider | ||
| stream that keeps `SPRITE_TOKEN` server-side and forwards opaque cmux protocol | ||
| messages. Do not send the provider token to the native client. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| usage() { | ||
| printf '%s\n' \ | ||
| 'Usage: build-sprite-base.sh [options]' \ | ||
| '' \ | ||
| 'Build a credential-free Fly Sprite checkpoint containing a pinned cmux remote' \ | ||
| 'daemon service.' \ | ||
| '' \ | ||
| 'Options:' \ | ||
| ' --org <slug> Sprite organization (default: SPRITE_ORG or manaflow)' \ | ||
| ' --name <name> Base Sprite name (default: cmux-tui-base-<timestamp>)' \ | ||
| ' --cmux-version <version> Pinned npm cmux version (default: 0.9.11)' \ | ||
| ' --keep-running Start the daemon after creating the clean checkpoint' \ | ||
| ' --keep-on-failure Do not destroy a newly created Sprite after failure' \ | ||
| ' -h, --help Show this help' \ | ||
| '' \ | ||
| 'Authentication:' \ | ||
| ' Source an owner-only file that exports SPRITE_TOKEN before running. The token' \ | ||
| ' is used only by the local Sprite CLI and is never copied into the Sprite or' \ | ||
| ' checkpoint.' | ||
| } | ||
|
|
||
| org="${SPRITE_ORG:-manaflow}" | ||
| name="" | ||
| cmux_version="0.9.11" | ||
| keep_running=0 | ||
| keep_on_failure=0 | ||
|
|
||
| while [[ $# -gt 0 ]]; do | ||
| case "$1" in | ||
| --org) | ||
| org="${2:?missing value for --org}" | ||
| shift 2 | ||
| ;; | ||
| --name) | ||
| name="${2:?missing value for --name}" | ||
| shift 2 | ||
| ;; | ||
| --cmux-version) | ||
| cmux_version="${2:?missing value for --cmux-version}" | ||
| shift 2 | ||
| ;; | ||
| --keep-running) | ||
| keep_running=1 | ||
| shift | ||
| ;; | ||
| --keep-on-failure) | ||
| keep_on_failure=1 | ||
| shift | ||
| ;; | ||
| -h|--help) | ||
| usage | ||
| exit 0 | ||
| ;; | ||
| *) | ||
| echo "unknown argument: $1" >&2 | ||
| usage >&2 | ||
| exit 2 | ||
| ;; | ||
| esac | ||
| done | ||
|
|
||
| if [[ ! "$cmux_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([+-][0-9A-Za-z.-]+)?$ ]]; then | ||
| echo "--cmux-version must be an exact semver version" >&2 | ||
| exit 2 | ||
| fi | ||
| if [[ -z "${SPRITE_TOKEN:-}" ]]; then | ||
| echo "SPRITE_TOKEN is required" >&2 | ||
| exit 2 | ||
|
Comment on lines
+69
to
+71
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect the referenced script around the reported validation and the surrounding
# output behavior without executing repository code.
if [ -f cmux-tui/scripts/build-sprite-base.sh ]; then
nl -ba cmux-tui/scripts/build-sprite-base.sh | sed -n '1,140p'
printf '\n--- occurrences of SPRITE_TOKEN ---\n'
rg -n 'SPRITE_TOKEN|sprite|echo ".*error|>&2' cmux-tui/scripts/build-sprite-base.sh || true
else
printf 'Referenced file not found\n'
fi
printf '\n--- shell syntax safety probe ---\n'
sh -n cmux-tui/scripts/build-sprite-base.sh && echo "syntax ok" || echo "syntax error"Repository: manaflow-ai/cmux Length of output: 194 Information Disclosure (CWE-209): Generation of Error Message Containing Sensitive Information Reachability: Internal Do not expose the credential environment-variable name in the error. The script writes 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| fi | ||
| if ! command -v sprite >/dev/null 2>&1; then | ||
| echo "sprite CLI is required: curl -fsSL https://sprites.dev/install.sh | bash" >&2 | ||
| exit 2 | ||
| fi | ||
|
|
||
| if [[ -z "$name" ]]; then | ||
| name="cmux-tui-base-$(date -u +%Y%m%d-%H%M%S)" | ||
| fi | ||
| if [[ ! "$name" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then | ||
| echo "--name must contain lowercase letters, digits, and hyphens" >&2 | ||
| exit 2 | ||
| fi | ||
|
|
||
| created=0 | ||
| cleanup() { | ||
| local status=$? | ||
| if [[ "$status" -ne 0 && "$created" -eq 1 && "$keep_on_failure" -eq 0 ]]; then | ||
| sprite destroy -o "$org" -s "$name" --force >/dev/null 2>&1 || true | ||
| fi | ||
| exit "$status" | ||
| } | ||
| trap cleanup EXIT | ||
|
|
||
| sprite create -o "$org" --skip-console "$name" | ||
| created=1 | ||
|
|
||
| # The Sprite edge is only the carrier. cmux authenticates and encrypts every | ||
| # connection with Noise and an enrolled device key. No Sprite management token | ||
| # or static cmux token is embedded in the image. | ||
| sprite url update -o "$org" -s "$name" --auth public | ||
|
|
||
| sprite exec -o "$org" -s "$name" -- bash -lc " | ||
| set -euo pipefail | ||
| npm install -g --omit=dev --no-audit --fund=false 'cmux@${cmux_version}' | ||
| sudo ln -sf \"\$(npm prefix -g)/bin/cmux\" /usr/local/bin/cmux | ||
| /usr/local/bin/cmux --version | ||
| install -d -m 700 /home/sprite/.local/share/cmux-sprite | ||
| sprite-env services create cmux-tui \ | ||
| --cmd /usr/local/bin/cmux \ | ||
| --args daemon,--session,sprite,--remote-ws,0.0.0.0:8080,--remote-ws-insecure-bind,--remote-state-dir,/home/sprite/.local/share/cmux-sprite/remote \ | ||
| --dir /home/sprite \ | ||
| --http-port 8080 \ | ||
| --duration 10s | ||
| sprite-env services stop cmux-tui | ||
| rm -rf /home/sprite/.local/share/cmux-sprite/remote /tmp/cmux-tui-* | ||
| rm -f /.sprite/logs/services/cmux-tui.log | ||
| install -d -m 700 /home/sprite/.local/share/cmux-sprite | ||
| test ! -e /home/sprite/.local/share/cmux-sprite/remote | ||
| " | ||
|
|
||
| checkpoint_output="$( | ||
| sprite checkpoint create -o "$org" -s "$name" \ | ||
| --comment "cmux ${cmux_version} clean base; no daemon identity, invitations, or credentials" | ||
| )" | ||
| printf '%s\n' "$checkpoint_output" | ||
| checkpoint="$( | ||
| printf '%s\n' "$checkpoint_output" | | ||
| awk '/Checkpoint v[0-9]+ created/ { for (i = 1; i <= NF; i++) if ($i ~ /^v[0-9]+$/) print $i }' | | ||
| tail -1 | ||
| )" | ||
| if [[ -z "$checkpoint" ]]; then | ||
| echo "checkpoint creation returned no checkpoint id" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| if [[ "$keep_running" -eq 1 ]]; then | ||
| sprite exec -o "$org" -s "$name" -- sprite-env services start cmux-tui | ||
| fi | ||
|
|
||
| trap - EXIT | ||
| printf '\nSPRITE_BASE_ORG=%s\n' "$org" | ||
| printf 'SPRITE_BASE_NAME=%s\n' "$name" | ||
| printf 'SPRITE_BASE_CHECKPOINT=%s\n' "$checkpoint" | ||
| printf 'SPRITE_BASE_CMUX_VERSION=%s\n' "$cmux_version" | ||
| printf 'SPRITE_BASE_DAEMON_STATE=absent\n' | ||
| printf 'SPRITE_BASE_SERVICE=%s\n' "$([[ "$keep_running" -eq 1 ]] && echo running || echo stopped)" | ||
|
Comment on lines
+143
to
+148
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Report the checkpoint build with the required tagged link. Lines 143-148 report build metadata but do not emit As per coding guidelines, shell builds must use the tagged localhost report link. 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Run invitation creation inside the Sprite.
This block runs
cmux enroll createon the reader host, but--state-dir /home/sprite/...identifies daemon state inside the Sprite. The command will target a local admin socket or fail. Wrap this command in authenticatedsprite exec -o <org> -s <sprite> -- ..., or state that the backend performs that exact operation before it returns the invitation.The later approval step already specifies authenticated Sprite exec. The creation step needs the same execution boundary.
🤖 Prompt for AI Agents