Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .bb/skills/verify-bb/INVENTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ has been exercised or that every behavior has been understood.
1 source files. Recipes: [hosts-updates](features/hosts-updates.md).

- `cli-command: apps/cli/src/commands/machine.ts: install <id-or-name> <provider>`
- `cli-command: apps/cli/src/commands/machine.ts: join-code`
- `cli-command: apps/cli/src/commands/machine.ts: list`
- `cli-command: apps/cli/src/commands/machine.ts: machine`
- `cli-command: apps/cli/src/commands/machine.ts: provider-cli`
Expand Down
2 changes: 1 addition & 1 deletion .bb/skills/verify-bb/features/hosts-updates.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ command’s `--help` before mutation. Use fresh browser snapshots for controls.
| Feature | Drive | Observable success |
| --- | --- | --- |
| List, inspect, rename | Compare Machines with machine list/show; rename a disposable host and reload. | Stable host ID is preserved; name and connection state agree. |
| Pair and enroll | Create machine join-code and redeem on the disposable host; attempt expired/reused code. | Exactly one intended host enrolls; invalid or consumed codes do not enroll another. |
| Pair and enroll | Run `bb machine create --provider manual`, redeem its enrollment command on the disposable host, then retry the expired or consumed command. | Exactly one intended host enrolls; expired or consumed commands do not enroll another. |
| Permission ceiling | Change the disposable machine ceiling and request a more permissive thread. | Host ceiling is enforced across UI, CLI, and runtime rather than merely hidden in the picker. |
| Disconnect and reconnect | Stop only the disposable daemon, observe unavailable host, restart it, and retry a targeted read. | Status and routing recover to the same host; offline operations do not route to a different machine. |
| Suspend and resume | Suspend a disposable provider-managed machine, send a thread follow-up to wake it, then repeat with the prompt banner's Resume action. | Every wake exposes a durable `resuming` lifecycle phase; the prompt banner says “Machine is resuming…” until the machine becomes active, and queued work dispatches once. |
Expand Down
17 changes: 10 additions & 7 deletions apps/cli/src/__tests__/bb-cli-skill-coverage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@ const BB_CLI_SKILL_ROOT = fileURLToPath(
);

function commandPaths(command: Command, prefix: string[] = []): string[] {
return command.commands.flatMap((child) => {
const path = [...prefix, child.name()];
const aliases = child
.aliases()
.map((alias) => [...prefix, alias].join(" "));
return [path.join(" "), ...aliases, ...commandPaths(child, path)];
});
const visible = new Set(command.createHelp().visibleCommands(command));
return command.commands
.filter((child) => visible.has(child))
.flatMap((child) => {
const path = [...prefix, child.name()];
const aliases = child
.aliases()
.map((alias) => [...prefix, alias].join(" "));
return [path.join(" "), ...aliases, ...commandPaths(child, path)];
});
}

function readMarkdownTree(directory: string): string {
Expand Down
25 changes: 13 additions & 12 deletions apps/cli/src/commands/machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,19 @@ export function registerMachineCommands(
}),
);

machine
.command("join-code", { hidden: true })
.description("Compatibility notice for removed machine join codes")
.option("--json", "Print machine-readable JSON output")
.action(
action(async () => {
throw new CliExitError("bb machine join-code has been removed.", 1, {
code: "removed_command",
hint: "Use `bb machine create --provider manual` and run the printed enrollment command.",
});
}),
);

machine
.command("create")
.description("Create a machine using an installed provider")
Expand Down Expand Up @@ -372,18 +385,6 @@ export function registerMachineCommands(
}),
);

machine
.command("join-code")
.description("Create a short-lived machine pairing code")
.option("--json", "Print machine-readable JSON output")
.action(
action(async (opts: MachineListCommandOptions) => {
const result = await createCliBbSdk(getUrl()).hosts.createJoinCode();
if (outputJson(opts, result)) return;
console.log(result.joinCode);
}),
);

machine
.command("rename <id-or-name> <name>")
.description("Rename an execution machine")
Expand Down
98 changes: 16 additions & 82 deletions apps/server/src/assets/install-machine.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,28 @@ set -eu

usage() {
cat >&2 <<'EOF'
Usage: install.sh --join-code <code> --host-id <host-id> --server <url> [--machine-code <code>] [--host-daemon-port <port>]
install.sh --bootstrap-env <NAME>
Usage: install.sh --bootstrap-env <NAME> [--host-daemon-port <port>]
install.sh --start|--stop|--uninstall --host-id <host-id> [--server-url <url>] [--data-dir <path>]

The first three options are required. --machine-code is required through bb connect.
Machines enroll from a private bootstrap bundle. Get the one-line command that
carries it from Settings -> Machines -> Add a machine, or from
`bb machine create --provider manual`. That command works through bb connect,
Tailscale, and any other address machines can reach.
By default, the installer assigns this enrolled daemon its own local API port.
EOF
exit 2
}

bootstrap_env=
join_code=
host_id=
server_url=
machine_code=
requested_host_daemon_port=
lifecycle_action=
requested_data_dir=

CURL_CONNECT_TIMEOUT_SECONDS=10
PACKAGE_DOWNLOAD_TIMEOUT_SECONDS=300
PACKAGE_DOWNLOAD_RETRIES=3
MACHINE_CODE_REDEEM_TIMEOUT_SECONDS=30
DAEMON_WAIT_ATTEMPTS=60
WAIT_PROGRESS_EVERY_ATTEMPTS=5

Expand Down Expand Up @@ -304,16 +303,13 @@ run_lifecycle() {

while [ "$#" -gt 0 ]; do
case "$1" in
--bootstrap-env|--join-code|--host-id|--server|--server-url|--machine-code|--host-daemon-port|--data-dir)
--bootstrap-env|--host-id|--server-url|--host-daemon-port|--data-dir)
[ "$#" -ge 2 ] || usage
[ -n "$2" ] || usage
case "$1" in
--bootstrap-env) bootstrap_env=$2 ;;
--join-code) join_code=$2 ;;
--host-id) host_id=$2 ;;
--server) server_url=$2 ;;
--server-url) server_url=$2 ;;
--machine-code) machine_code=$2 ;;
--host-daemon-port) requested_host_daemon_port=$2 ;;
--data-dir) requested_data_dir=$2 ;;
esac
Expand All @@ -333,9 +329,10 @@ while [ "$#" -gt 0 ]; do
done

if [ -n "$lifecycle_action" ]; then
[ -z "$bootstrap_env$join_code$machine_code$requested_host_daemon_port" ] || usage
elif [ -n "$bootstrap_env" ]; then
if [ -n "$join_code$host_id$server_url$machine_code" ]; then usage; fi
[ -z "$bootstrap_env$requested_host_daemon_port" ] || usage
else
[ -n "$bootstrap_env" ] || usage
if [ -n "$host_id$server_url" ]; then usage; fi
host_id=$(node -e '
const name = process.argv[1];
if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name)) process.exit(2);
Expand All @@ -355,8 +352,6 @@ elif [ -n "$bootstrap_env" ]; then
' "$bootstrap_env") || usage
bootstrap_payload=$(node -e 'process.stdout.write(process.env[process.argv[1]])' "$bootstrap_env")
unset "$bootstrap_env"
else
[ -n "$join_code" ] || usage
fi
[ -n "$host_id" ] || usage
if [ -z "$lifecycle_action" ]; then [ -n "$server_url" ] || usage; fi
Expand Down Expand Up @@ -733,72 +728,12 @@ fi

bb_cli="${bb_app%/*}/bb"
if [ ! -x "$bb_cli" ]; then bb_cli=$(command -v bb || true); fi
if [ -n "$bootstrap_env" ]; then
if [ -z "$bb_cli" ]; then
fail_step "The installed build does not provide the machine enrollment CLI."
exit 1
fi
BB_ENROLLMENT="$bootstrap_payload" BB_DATA_DIR="$data_dir" "$bb_cli" machine enroll --bootstrap-env BB_ENROLLMENT
bootstrap_payload=
fi

if [ -n "$machine_code" ]; then
connect_apex=$(node -e '
const url = new URL(process.argv[1]);
const labels = url.hostname.split(".");
if (labels.length < 3) process.exit(2);
url.hostname = labels.slice(1).join(".");
url.pathname = "/";
url.search = "";
url.hash = "";
process.stdout.write(url.origin);
' "$server_url" 2>/dev/null) || {
fail_step "Could not derive the bb connect apex from $server_url."
exit 1
}
active_step "Authorizing this machine with bb connect"
redeem_response=$(curl -fsS \
--connect-timeout "$CURL_CONNECT_TIMEOUT_SECONDS" \
--max-time "$MACHINE_CODE_REDEEM_TIMEOUT_SECONDS" \
-X POST \
-H 'content-type: application/json' \
--data "{\"code\":\"$machine_code\"}" \
"$connect_apex/api/connect/redeem-machine") || {
fail_step "Could not redeem the bb connect machine code."
exit 1
}
printf '%s' "$redeem_response" | node -e '
let input = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => { input += chunk; });
process.stdin.on("end", () => {
const body = JSON.parse(input);
if (typeof body.credential !== "string" || !body.credential.startsWith("bbcm_")) {
process.exit(2);
}
if (typeof body.machineId !== "string" || body.machineId.length === 0) {
process.exit(2);
}
const fs = require("node:fs");
const path = require("node:path");
const [dataDir, serverUrl] = process.argv.slice(1);
const configPath = path.join(dataDir, "config.json");
let config = {};
try { config = JSON.parse(fs.readFileSync(configPath, "utf8")); }
catch (error) { if (error.code !== "ENOENT") throw error; }
config.serverUrl = serverUrl;
config.machineCredential = body.credential;
config.connectMachineId = body.machineId;
const temporary = `${configPath}.${process.pid}.tmp`;
fs.writeFileSync(temporary, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
fs.renameSync(temporary, configPath);
});
' "$data_dir" "$server_url" || {
fail_step "The bb connect machine-code response was invalid."
exit 1
}
complete_step "Authorized this machine with bb connect"
if [ -z "$bb_cli" ]; then
fail_step "The installed build does not provide the machine enrollment CLI."
exit 1
fi
BB_ENROLLMENT="$bootstrap_payload" BB_DATA_DIR="$data_dir" "$bb_cli" machine enroll --bootstrap-env BB_ENROLLMENT
bootstrap_payload=

auth_matches_host() {
node -e '
Expand Down Expand Up @@ -837,7 +772,6 @@ if [ "$already_joined" = no ]; then
BB_APP_NPM_PREFIX="$bb_app_npm_prefix" BB_DATA_DIR="$data_dir" nohup "$bb_app" host-daemon join \
--auto-update \
--host-daemon-port "$host_daemon_port" \
--join-code "$join_code" \
--host-id "$host_id" \
--server-url "$server_url" >"$join_log" 2>&1 &
join_pid=$!
Expand Down Expand Up @@ -878,7 +812,7 @@ if [ "$platform" = linux ] && [ "$(id -u)" = 0 ] &&
! systemd-detect-virt --container --quiet >/dev/null 2>&1; then
systemd_scope=--system
fi
if [ -n "$bootstrap_env" ] && [ "$platform" = linux ] &&
if [ "$platform" = linux ] &&
[ "$systemd_scope" = --user ] && ! systemctl --user show-environment >/dev/null 2>&1; then
BB_INSTALL_SKIP_SERVICE=1
fi
Expand Down
Loading
Loading