Skip to content

Commit f4da6fc

Browse files
feat(deploy): add deploy vps command
Deploy an SSH-able VPS on a processor without an acurast.json: vendors the app-tunnel/cargo example (Shell runtime, dropbear over the Acurast reverse tunnel) as a CLI-shipped template and synthesizes the project config from flags, VPS_* env vars, or an interactive wizard (precedence in that order). - extract executeDeployFlow from the deploy action so both commands share signer/balance/pricing/submission logic - enable positional options so flags shared with `deploy` (--dry-run, --min-memory, ...) reach the subcommand - image aliases: ubuntu24 (noble, default) / ubuntu25 (questing), pinned proot-distro tarballs with sha256 - SSH auth: password and/or authorized key (SSH_AUTHORIZED_KEY written to authorized_keys by the template's start.sh)
1 parent 9bc9122 commit f4da6fc

16 files changed

Lines changed: 1975 additions & 657 deletions

File tree

README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ To use the Acurast CLI, type `acurast` followed by any of the available options
4646

4747
- `new <project-name>` - Create a new Acurast project from a template.
4848
- `deploy [options] [project]` - Deploy the current project to the Acurast platform.
49+
- `deploy vps [options]` - Deploy an SSH-able VPS on an Acurast processor (see [Deploy a VPS](#deploy-a-vps)).
4950
- `cancel <deployment-id> [options]` - Cancel (deregister) a deployment on-chain and return any unused locked funds.
5051
- `estimate-fee [options] [project]` - Estimate the fee for the current project.
5152
- `deployments [arg] [options]` - List, view, and manage deployments.
@@ -275,6 +276,59 @@ ACURAST_DEVNET_INDEXER_API_KEY=<your-key>
275276

276277
Then in `acurast.json` set `"network": "devnet"` on the project entry.
277278

279+
## Deploy a VPS
280+
281+
`acurast deploy vps` turns a processor into an SSH-able "VPS": it deploys a Shell-runtime app (based on the [app-tunnel example](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-tunnel/cargo)) that runs an Ubuntu rootfs, starts a [Dropbear](https://github.com/mkj/dropbear) SSH server and exposes it through the Acurast reverse tunnel. No `acurast.json` is needed — the app ships with the CLI.
282+
283+
```bash
284+
acurast deploy vps \
285+
--image ubuntu24 \
286+
--min-memory 2GB \
287+
--min-storage 10GB \
288+
--min-compute-score 100 \
289+
--authorized-ssh-key "ssh-ed25519 AAAA... user@host" \
290+
--duration 24h \
291+
--callback-url https://webhook.watch/your-endpoint
292+
```
293+
294+
Running `acurast deploy vps` without flags starts an interactive wizard that asks for anything missing. `ACURAST_MNEMONIC` must be set (or use `acurast login` for remote signing).
295+
296+
### Options
297+
298+
| Flag | Env var | Description |
299+
| -------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------- |
300+
| `--image <alias>` | `VPS_IMAGE` | `ubuntu24` (24.04 LTS, default) or `ubuntu25` (25.10). proot-distro rootfs tarballs, aarch64. |
301+
| `--min-memory <size>` | `VPS_MIN_MEMORY` | Minimum total RAM of the processor (e.g. `2GB`). |
302+
| `--min-storage <size>` | `VPS_MIN_STORAGE` | Minimum available storage (e.g. `10GB`). |
303+
| `--min-compute-score <n>` | `VPS_MIN_COMPUTE_SCORE` | Minimum CPU single-core benchmark score. |
304+
| `--authorized-ssh-key <k>` | `VPS_AUTHORIZED_SSH_KEY` | SSH public key appended to `/root/.ssh/authorized_keys`. |
305+
| `--ssh-password <pw>` | `VPS_SSH_PASSWORD` | Root password. Defaults to `password` — set your own for anything sensitive. |
306+
| `--duration <dur>` | `VPS_DURATION` | How long the VPS runs (e.g. `1h`, `24h`, `2d`). Default `24h`. |
307+
| `--callback-url <url>` | `VPS_CALLBACK_URL` | Webhook receiving `log`/`started`/`error` events; the `started` event contains the SSH connect command. |
308+
| `--network <net>` | `VPS_NETWORK` | `mainnet`, `canary` (default) or `devnet`. |
309+
| `--replicas <n>` | `VPS_REPLICAS` | Number of VPS instances. Default 1. |
310+
| `--max-cost-per-execution` | `VPS_MAX_COST_PER_EXECUTION` | Reward per execution in the smallest token unit. |
311+
312+
Flags win over `VPS_*` environment variables (which can live in your `.env`); the wizard only asks for values that neither provides. `--dry-run`, `--non-interactive`, `--only-upload`, `--exit-early` and `--output json` work like they do for `acurast deploy`.
313+
314+
### Connecting
315+
316+
The tunnel identity is generated on the processor, so the public hostname is only known once the deployment starts. Set `--callback-url` (e.g. a [webhook.watch](https://webhook.watch) endpoint) and wait for the `started` event:
317+
318+
```json
319+
{
320+
"event": "started",
321+
"webUrl": "https://<clientId>.<domain>",
322+
"sshUrl": "https://<secondaryClientId>.<domain>",
323+
"sshPort": 2222,
324+
"connect": "ssh -o ProxyCommand='openssl s_client -quiet -servername ... -connect ...:443' root@<secondaryClientId>"
325+
}
326+
```
327+
328+
Run the `connect` command to open the SSH session (SSH is wrapped in TLS via the tunnel's secondary connection). Without a callback URL, the same information is printed in the deployment logs — `acurast devtools <deployment-id>` gives you a log viewer URL. The deployment also serves a small status page over the primary tunnel connection (`webUrl`).
329+
330+
Requires processors running Acurast v1.26.0 or later (`minProcessorVersions.android` is set automatically).
331+
278332
## Live Code Feature
279333

280334
For easier development of acurast deployments, we added a feature that we call "Live Code". To use this feature, you can dedicate one or multiple processors to run a piece of code for an extended period of time, which can then on-demand execute your code and return the result. This makes development and debugging a lot faster because you can see console.logs and errors.

e2e/scripts/lib/run-cli.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ run_new_interactive() {
4141
expect "$E2E_LIB_DIR/new.exp" "$@"
4242
}
4343

44+
# Drive the real interactive `acurast deploy vps` wizard with --dry-run.
45+
# Usage: run_vps_interactive_dry_run
46+
run_vps_interactive_dry_run() {
47+
_require_expect
48+
expect "$E2E_LIB_DIR/vps.exp" "$@"
49+
}
50+
4451
# Build a throwaway local git template repo so `acurast new` runs fully offline
4552
# and its interactive template picker is deterministic (a single choice, so
4653
# Enter always selects it). Exports ACURAST_TEMPLATES_REPO pointing at it.

e2e/scripts/lib/vps.exp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/usr/bin/env expect
2+
# Drive the interactive `acurast deploy vps` wizard with --dry-run.
3+
# Usage: expect vps.exp
4+
# Requires ACURAST_MNEMONIC in the environment.
5+
6+
# A real terminal size and TERM are required, otherwise the CLI's ora spinner
7+
# re-renders in a tight loop and floods the pty.
8+
set stty_init "rows 40 cols 120"
9+
set env(TERM) "xterm-256color"
10+
11+
set timeout 120
12+
13+
spawn acurast deploy vps --dry-run
14+
15+
expect {
16+
timeout { puts "\nTIMEOUT: image prompt"; exit 2 }
17+
"Which image should the VPS run?"
18+
}
19+
send "\r"
20+
21+
expect {
22+
timeout { puts "\nTIMEOUT: duration prompt"; exit 2 }
23+
"How long should the VPS run?"
24+
}
25+
send "1h\r"
26+
27+
expect {
28+
timeout { puts "\nTIMEOUT: hardware requirements prompt"; exit 2 }
29+
"Set minimum hardware requirements"
30+
}
31+
send "\r"
32+
33+
expect {
34+
timeout { puts "\nTIMEOUT: ssh key prompt"; exit 2 }
35+
"Authorized SSH public key"
36+
}
37+
send "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIF7e2edummykey e2e@acurast\r"
38+
39+
expect {
40+
timeout { puts "\nTIMEOUT: password prompt"; exit 2 }
41+
"Root password"
42+
}
43+
send "\r"
44+
45+
expect {
46+
timeout { puts "\nTIMEOUT: callback url prompt"; exit 2 }
47+
"Callback URL"
48+
}
49+
send "\r"
50+
51+
expect {
52+
timeout { puts "\nTIMEOUT: VPS summary"; exit 2 }
53+
"Deploying VPS"
54+
}
55+
56+
expect {
57+
timeout { puts "\nTIMEOUT: dry-run line"; exit 2 }
58+
"Dry run, not deploying"
59+
}
60+
61+
expect eof
62+
catch wait result
63+
exit [lindex $result 3]

e2e/scripts/run-all.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ SCENARIOS=(
4242
"$SCRIPT_DIR/scenarios/04-estimate-fee.sh"
4343
"$SCRIPT_DIR/scenarios/05-new-nodejs.sh"
4444
"$SCRIPT_DIR/scenarios/06-new-init-deploy.sh"
45+
"$SCRIPT_DIR/scenarios/07-deploy-vps-dry-run.sh"
4546
)
4647

4748
for scenario in "${SCENARIOS[@]}"; do
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5+
# shellcheck source=../lib/assert.sh
6+
source "$SCRIPT_DIR/../lib/assert.sh"
7+
# shellcheck source=../lib/run-cli.sh
8+
source "$SCRIPT_DIR/../lib/run-cli.sh"
9+
10+
E2E_ROOT="${E2E_ROOT:?}"
11+
WORK_ROOT="${WORK_ROOT:?}"
12+
13+
# `deploy vps` needs no project files — the app template ships with the CLI.
14+
# Canary is required: the flow exits early on mainnet when the balance is 0.
15+
SCENARIO_DIR="$WORK_ROOT/deploy-vps"
16+
rm -rf "$SCENARIO_DIR"
17+
mkdir -p "$SCENARIO_DIR"
18+
cd "$SCENARIO_DIR"
19+
20+
# Non-interactive: everything from flags.
21+
OUT="$(run_acurast deploy vps --non-interactive --dry-run \
22+
--image ubuntu24 --min-memory 2GB --min-storage 10GB --min-compute-score 100 \
23+
--authorized-ssh-key "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIF7e2edummykey e2e@acurast" \
24+
--duration 1h --output json 2>&1)"
25+
CODE=$?
26+
assert_exit "$CODE" 0 "acurast deploy vps --dry-run (non-interactive)"
27+
assert_stdout_contains "$OUT" "Dry run, not deploying"
28+
assert_no_deploy_artifacts "$SCENARIO_DIR"
29+
30+
# VPS_* env vars: flags win, env fills the rest.
31+
OUT="$(VPS_MIN_MEMORY=2GB VPS_DURATION=1h VPS_SSH_PASSWORD=e2e-password \
32+
run_acurast deploy vps --non-interactive --dry-run --output json 2>&1)"
33+
CODE=$?
34+
assert_exit "$CODE" 0 "acurast deploy vps --dry-run (VPS_* env)"
35+
assert_stdout_contains "$OUT" "Dry run, not deploying"
36+
assert_no_deploy_artifacts "$SCENARIO_DIR"
37+
38+
# Interactive wizard driven through a pseudo-tty.
39+
OUT="$(run_vps_interactive_dry_run 2>&1)"
40+
CODE=$?
41+
assert_exit "$CODE" 0 "acurast deploy vps --dry-run (interactive wizard)"
42+
assert_stdout_contains "$OUT" "Dry run, not deploying"
43+
assert_no_deploy_artifacts "$SCENARIO_DIR"
44+
45+
echo "OK: deploy vps dry-run (non-interactive, env vars, wizard)"

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
],
1313
"scripts": {
1414
"start": "npm run build && node dist/index.js",
15-
"build": "npx tsc && node -e \"const fs=require('fs');fs.copyFileSync('./src/util/live-code-processor.js','./dist/util/live-code-processor.js');fs.chmodSync('./dist/index.js',0o755)\"",
15+
"build": "npx tsc && node -e \"const fs=require('fs');fs.copyFileSync('./src/util/live-code-processor.js','./dist/util/live-code-processor.js');fs.cpSync('./src/templates','./dist/templates',{recursive:true});fs.chmodSync('./dist/index.js',0o755)\"",
1616
"setup": "npm run build && npm i -g .",
1717
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
1818
"test:e2e": "sh -c 'if command -v docker-compose >/dev/null 2>&1; then docker-compose -f e2e/docker-compose.yml run --rm e2e; else docker compose -f e2e/docker-compose.yml run --rm e2e; fi'",

0 commit comments

Comments
 (0)