Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
bd954e1
feat(): implement e2e test
kaltra-muho May 25, 2026
145a526
fix(): set-env issue
kaltra-muho May 26, 2026
f8bb7c6
feat(): implement cli playground
kaltra-muho Jun 2, 2026
2debe48
feat: remove template reference
AndreasGassmann Jul 1, 2026
4eca5ad
feat: update sdk to 1.3.0
AndreasGassmann Jul 1, 2026
2ae0b1a
Merge pull request #138 from Acurast/fix/set-env
AndreasGassmann Jul 1, 2026
033b48b
feat: update tests to use pseudo-tty
AndreasGassmann Jul 6, 2026
166c62b
fix: clearer error message
AndreasGassmann Jul 6, 2026
8af2f52
Merge pull request #137 from Acurast/feat/e2e-test
AndreasGassmann Jul 6, 2026
d60cdeb
fix(test): run jest in ESM mode so playground tests pass
AndreasGassmann Jul 6, 2026
f1bbf6f
Merge remote-tracking branch 'origin/develop' into feat/cli-playground
AndreasGassmann Jul 6, 2026
48f43c1
docs: design for global browser-wallet auth + signing-mode selection
AndreasGassmann Jul 6, 2026
76aa2b2
feat(auth): global browser-wallet login + signing-mode selection
AndreasGassmann Jul 6, 2026
e93e5ce
fix(login): keep the local callback server alive; make built bin exec…
AndreasGassmann Jul 6, 2026
fc28253
fix(login): allow the hub's cross-site top-level redirect to /callback
AndreasGassmann Jul 6, 2026
d8191fa
fix(e2e): keep init non-interactive-safe and drive the new signing pr…
AndreasGassmann Jul 6, 2026
9bc9122
Merge pull request #139 from Acurast/feat/cli-playground
AndreasGassmann Jul 6, 2026
f4da6fc
feat(deploy): add `deploy vps` command
AndreasGassmann Jul 6, 2026
32d0697
feat(deploy-vps): ask for network in interactive wizard
AndreasGassmann Jul 6, 2026
ba777d3
fix(deploy-vps): drop DevTools hint, guard deploys without callback URL
AndreasGassmann Jul 6, 2026
fb15658
fix(deploy): gate env-var wait on actual envVars, not config field
AndreasGassmann Jul 6, 2026
47a148a
feat(deploy-vps): offer to persist wizard answers to .env
AndreasGassmann Jul 6, 2026
c6208c3
fix(deploy): don't abort dry runs on insufficient benchmark match
AndreasGassmann Jul 6, 2026
d6ddf70
feat: use sdk for vps
AndreasGassmann Jul 16, 2026
cdedff5
feat: add web-url
AndreasGassmann Jul 20, 2026
20e1545
test(e2e): use ubuntu image alias for deploy-vps dry-run
AndreasGassmann Jul 20, 2026
d07d1fc
fix: e2e tests
AndreasGassmann Jul 20, 2026
6fc5196
test(e2e): align deploy-vps wizard with key-only auth and single image
AndreasGassmann Jul 20, 2026
90bdb2c
chore: release 0.10.0
AndreasGassmann Jul 20, 2026
2a33ca4
Merge pull request #141 from Acurast/feat/deploy-vps
AndreasGassmann Jul 20, 2026
4fcea8d
Merge pull request #143 from Acurast/release/v0.10.0
AndreasGassmann Jul 20, 2026
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
9 changes: 9 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Used when building the e2e image (docker-compose build context is repo root).
node_modules
dist
.git

e2e/.env
e2e/fixtures/**/node_modules
e2e/fixtures/**/dist
e2e/fixtures/**/package-lock.json
35 changes: 35 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: E2E

on:
push:
pull_request:
workflow_dispatch:

jobs:
e2e:
name: Docker E2E
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Check e2e secret
id: secret
run: |
if [[ -z "${{ secrets.ACURAST_E2E_MNEMONIC }}" ]]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "::warning title=E2E skipped::ACURAST_E2E_MNEMONIC secret not set — e2e tests did NOT run (expected for fork PRs; set the secret on the main repo to run them)."
{
echo "## ⚠️ E2E tests skipped"
echo ""
echo "\`ACURAST_E2E_MNEMONIC\` secret is not set, so the Docker e2e suite **did not run**."
echo "This is expected for fork PRs. On the main repo, set the secret to run the tests."
} >> "$GITHUB_STEP_SUMMARY"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi

- name: Run e2e in Docker
if: steps.secret.outputs.skip != 'true'
env:
ACURAST_E2E_MNEMONIC: ${{ secrets.ACURAST_E2E_MNEMONIC }}
run: npm run test:e2e
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,9 @@ node_modules

.env
.acurast
settings.json
settings.json
e2e/.env
e2e/.work
e2e/fixtures/**/node_modules
e2e/fixtures/**/dist
e2e/fixtures/**/package-lock.json
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ To use the Acurast CLI, type `acurast` followed by any of the available options

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

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

## Deploy a VPS

`acurast deploy vps` turns a processor into an SSH-able "VPS": it deploys the pinned tunnel bundle from [`@acurast/vps`](https://github.com/Acurast/acurast-typescript-sdk/tree/main/packages/acurast-vps), which 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, and nothing is uploaded to IPFS — the bundle is already pinned.

The tunnel keypair is generated locally, so the SSH hostname is known before the deployment even starts: the CLI prints it upfront, waits for the VPS to boot and then prints the ready-to-paste SSH connect command.

```bash
acurast deploy vps \
--min-memory 2GB \
--min-storage 10GB \
--min-compute-score 100 \
--authorized-ssh-key "ssh-ed25519 AAAA... user@host" \
--duration 24h
```

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).

### Options

| Flag | Env var | Description |
| -------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------- |
| `--image <alias>` | `VPS_IMAGE` | Image preset. Currently `ubuntu` (25.10, proot-distro rootfs, aarch64). |
| `--min-memory <size>` | `VPS_MIN_MEMORY` | Minimum total RAM of the processor (e.g. `2GB`). |
| `--min-storage <size>` | `VPS_MIN_STORAGE` | Minimum available storage (e.g. `10GB`). |
| `--min-compute-score <n>` | `VPS_MIN_COMPUTE_SCORE` | Minimum CPU single-core benchmark score. |
| `--min-cpu-multi-score <n>` | `VPS_MIN_CPU_MULTI_SCORE` | Minimum CPU multi-core benchmark score. |
| `--authorized-ssh-key <k>` | `VPS_AUTHORIZED_SSH_KEY` | SSH public key appended to `/root/.ssh/authorized_keys`. Required — the VPS is key-auth only. |
| `--duration <dur>` | `VPS_DURATION` | How long the VPS runs (e.g. `1h`, `24h`, `2d`). Default `24h`. |
| `--callback-url <url>` | `VPS_CALLBACK_URL` | Optional webhook receiving `log`/`started`/`error` events as JSON. |
| `--http-port <port>` | `VPS_HTTP_PORT` | Also serve plain HTTP from this VPS port on the same tunnel subdomain (>= 1024). |
| `--network <net>` | `VPS_NETWORK` | `mainnet`, `canary` (default) or `devnet`. |
| `--max-cost-per-execution` | `VPS_MAX_COST_PER_EXECUTION` | Reward per execution in the smallest token unit. |

Flags win over `VPS_*` environment variables (which can live in your `.env`); the wizard only asks for values that neither provides. At the end of the wizard the CLI offers to save your answers to `.env` as `VPS_*` variables, so subsequent runs skip the questions. `--dry-run`, `--non-interactive`, `--exit-early` and `--output json` work like they do for `acurast deploy`.

### Connecting

The tunnel keypair is generated locally and the clientId (the subdomain) is derived from it, so the CLI knows the hostname upfront — it is shown in the deploy summary as `https://<clientId>.<domain>`. After the deployment is submitted, the CLI polls the tunnel until Dropbear answers and then prints the connect command:

```
ssh -o ProxyCommand='openssl s_client -quiet -servername <clientId>.<domain> -connect <clientId>.<domain>:443' root@<clientId>.<domain>
```

SSH is wrapped in TLS behind the tunnel's Let's Encrypt certificate. With `--exit-early` the CLI prints the connect command without waiting. `--callback-url` is optional and additionally delivers `log`/`started`/`error` events to your webhook. With `--http-port`, the same subdomain also serves plain HTTP from that VPS port (protocol multiplexing via sslh).

Requires processors running Acurast v1.26.0 or later (`minProcessorVersions.android` is set automatically).

## Live Code Feature

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.
Expand Down
154 changes: 154 additions & 0 deletions docs/superpowers/specs/2026-07-06-global-auth-signing-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# Global browser-wallet auth + signing-mode selection

Date: 2026-07-06
Branch: `feat/cli-playground`
Status: approved design

## Problem

The CLI-playground feature added browser-wallet remote signing (`login`,
`logout`, `whoami`, `RemoteSigner`), but:

1. `acurast init` never offers it — onboarding writes a local `ACURAST_MNEMONIC`
to `.env` and steers every user to key-on-disk signing. Remote signing is
undiscoverable.
2. Login state is stored per-project. `ACURAST_BASE_PATH` is `./.acurast`
(cwd-relative, `src/constants.ts:8`), so `auth.json` lives inside each
project. Logging in for one project does not carry to others.
3. No account switching. `authStore` keeps a single record under key `auth`;
`setAuth` overwrites.
4. Signing mode is only written to the file log, never surfaced to the user, so
there is no visible signal that a local mnemonic (private key on disk) is in
use.

## Goals

- `init` asks how to sign, defaulting to browser wallet.
- Global logged-in state: log in once, every project without its own mnemonic
uses it.
- A project can pin a specific account (override the global login).
- Account switching by re-login (single active account).
- Deploy visibly reports the signing mode, calling out local-mnemonic use.

## Non-goals (YAGNI)

Multi-account registry, `account use <addr>`, org/team accounts, migrating any
existing per-project `auth.json` (feature is unreleased on this branch).

## Storage layout

| Path | Scope | Contents |
|------|-------|----------|
| `~/.acurast/auth.json` | global (new) | single active account, mode `0600` |
| `./.acurast/auth.json` | project pin (optional) | account pinned for this dir |
| `./.acurast/deploy`, `keys.json`, … | per-project (unchanged) | deployments, ECDH keys |

- New constant `ACURAST_GLOBAL_BASE_PATH` = `<os.homedir()>/.acurast`.
- Existing `ACURAST_BASE_PATH` (`./.acurast`) is unchanged.
- `LocalStorage` gains an optional base-directory argument so one class serves
both scopes:
`new LocalStorage(fileName, mode?, basePath = ACURAST_BASE_PATH)`.
The auth store passes the global path for global auth and the default (cwd)
path for the project pin.

`AuthRecord` shape is unchanged (address, signatureType, network?, loggedInAt,
lastUsedAt?). It stores **no private key** — signing is delegated to the
browser wallet.

## Resolution order (`getSigningMode`)

Highest priority first:

1. `ACURAST_SIGNING_MODE` env = `local` | `remote` — explicit force.
2. project pin `./.acurast/auth.json` present → **remote** (that account).
3. `ACURAST_MNEMONIC` present → **local**.
4. global `~/.acurast/auth.json` present → **remote**.
5. none → **local** (falls through to the existing deploy-time error that tells
the user to run `acurast login` or set `ACURAST_MNEMONIC`).

Rationale: an ambient global login must never silently change how an existing
mnemonic project signs (mnemonic wins over global login). A deliberate project
pin, however, does override a mnemonic.

## authStore refactor

```
getGlobalAuth(): AuthRecord | null // ~/.acurast/auth.json
getProjectAuth(): AuthRecord | null // ./.acurast/auth.json
getActiveAuth(): AuthRecord | null // project ?? global
getLoggedInAddress(): string | undefined // getActiveAuth()?.address
isLoggedIn(): boolean

setAuth(record, scope: 'global' | 'project' = 'global')
clearAuth(scope: 'global' | 'project') // plus an --all path in the command
touchAuth() // updates lastUsedAt on the active record's scope

getSigningMode(): 'local' | 'remote' // implements the order above
getAuthSource(): 'project' | 'global' | 'mnemonic' | 'none' // for whoami/deploy
```

Expiry (`SESSION_MAX_AGE_MS`, 14 days) applies per record, both scopes.

## Command surface

- `acurast login [--network <n>] [--project]`
Default writes global auth. `--project` writes the project pin. Switching
accounts = run `login` again (overwrites the active record in that scope).
- `acurast logout [--project] [--all]`
Default clears global active. `--project` clears the pin. `--all` clears both.
- `acurast whoami`
Prints active address, **source** (project pin / global / mnemonic / none),
and signing mode.

## init prompt

New first question in `init` (default = browser wallet):

```
How do you want to sign deployments?
> Browser wallet — no private key stored (recommended)
Local mnemonic — generate & store in .env
```

- **Browser wallet**: do not generate or write `ACURAST_MNEMONIC`; skip the
mnemonic/faucet output. If already globally logged in, print that address.
If not, offer to run the login flow now (opens the hub); otherwise instruct
the user to run `acurast login`.
- **Local mnemonic**: exactly the current behavior (generate, write to `.env`,
print address + faucet link).

## Deploy signing notice

Deploy currently logs `Signing mode: <mode>` only to the file logger
(`src/commands/deploy.ts:455`). Add a visible console line before signing:

- local: `Signing with local mnemonic (<address>) — private key is read from
your environment.`
- remote: `Signing with your browser wallet (<address>).`

The local wording makes key-on-disk use explicit every deploy.

## Testing

- **authStore**: full precedence matrix (all 5 cases), global vs project scope,
`getActiveAuth` override (project beats global), expiry, `getAuthSource`.
In-memory `LocalStorage` mock keyed by base directory so global and project
stores are independent.
- **init**: mode selection — browser path writes no `ACURAST_MNEMONIC`;
mnemonic path unchanged. Mock the prompt and fs.
- **login / logout**: `--project` and `--all` flags write/clear the correct
file/scope.
- **deploy notice**: asserts the correct console line per mode (unit-level on
the notice helper, to avoid a full deploy round-trip).

## Affected files

- `src/constants.ts` — add `ACURAST_GLOBAL_BASE_PATH`.
- `src/util/LocalStorage.ts` — optional base-dir arg.
- `src/util/authStore.ts` — scopes, resolution order, `getAuthSource`.
- `src/commands/login.ts` — `--project` flag + scoped `setAuth`.
- `src/commands/logout.ts` — `--project` / `--all`.
- `src/commands/whoami.ts` — source + mode output.
- `src/commands/init.ts` — signing-mode prompt.
- `src/commands/deploy.ts` — visible signing notice.
- Tests under `test/`.
5 changes: 5 additions & 0 deletions e2e/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Mirror of repo-root .dockerignore entries for e2e (build context is `..`, so root file applies).
.env
fixtures/**/node_modules
fixtures/**/dist
fixtures/**/package-lock.json
3 changes: 3 additions & 0 deletions e2e/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Copy to e2e/.env for local runs (never commit e2e/.env).
# Fund the derived address on canary: https://faucet.acurast.com
ACURAST_E2E_MNEMONIC="your twelve or twenty four word test mnemonic here"
22 changes: 22 additions & 0 deletions e2e/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM node:20-bookworm

RUN apt-get update && apt-get install -y --no-install-recommends \
git \
ca-certificates \
expect \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci

COPY . .
RUN npm run build && npm link

WORKDIR /workspace

ENV REPO_ROOT=/app
ENV E2E_ROOT=/app/e2e

ENTRYPOINT ["bash", "/app/e2e/scripts/run-all.sh"]
81 changes: 81 additions & 0 deletions e2e/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Acurast CLI — Docker E2E Tests

End-to-end tests run the real `acurast` CLI in an isolated Docker container against live canary infrastructure (RPC, matcher). Interactive commands (`init`, `new`) are driven through a pseudo-TTY, so the same Inquirer wizard a user sees is exercised step by step. Deploy steps always use `--dry-run` so no cACU is spent.

## Prerequisites

1. **Docker** and Docker Compose (or `docker compose`)
2. A **canary test mnemonic** with a small cACU balance (faucet: https://faucet.acurast.com)
3. Set `ACURAST_E2E_MNEMONIC` (never commit the real value)

Docker build context is the **repository root**; exclusions live in [`.dockerignore`](../.dockerignore) and [`e2e/.dockerignore`](.dockerignore).

### Derive the faucet address

From the repo root after `npm run build`:

```bash
node --input-type=module -e "
import { walletFromMnemonic } from '@acurast/sdk/chain';
const w = await walletFromMnemonic(process.env.ACURAST_E2E_MNEMONIC, { name: 'AcurastCli' });
console.log(w.address);
"
```

## Run locally

```bash
cp e2e/.env.example e2e/.env
# edit e2e/.env, then:
export $(grep -v '^#' e2e/.env | xargs)

npm run test:e2e
```

Uses `docker-compose` or `docker compose` depending on what is installed.

You can also run the scenarios on the host (CLI must be built and linked). Scenario workspaces go to `e2e/.work` (not `/workspace`):

```bash
npm run build && npm link
export $(grep -v '^#' e2e/.env | xargs)
npm run test:e2e:local
```

The interactive scenarios need `expect(1)` to drive the prompts.

## How interactive prompts are driven

Inquirer prompts read keypresses in raw mode and pause stdin between prompts, so answers can't be piped ahead of time — they must be sent one prompt at a time. `expect(1)` handles that synchronisation via `scripts/lib/init.exp` and `scripts/lib/new.exp` (wrapped by `run_init_interactive` / `run_new_interactive`). No production CLI flags are added to bypass prompts.

`acurast new` clones its templates over the network. To keep the picker deterministic and offline, `setup_template_repo` builds a throwaway local git repo with a single template and points `ACURAST_TEMPLATES_REPO` at it.

## Scenarios

| Script | Description |
|--------|-------------|
| `00-smoke` | `--version`, `--help` |
| `01-init-greenfield` | Interactive `acurast init` on a blank fixture |
| `02-init-existing-json` | `acurast init` when `acurast.json` already exists (no prompts) |
| `03-blank-project-build-deploy-dry-run` | Build canary fixture, `deploy --dry-run` |
| `04-estimate-fee` | `estimate-fee` using repo root `acurast.json` |
| `05-new-nodejs` | Interactive `acurast new` against a local template repo |
| `06-new-init-deploy` | Interactive `new` → interactive `init` → build → deploy dry-run |

## CLI flags used in e2e

| Flag | Purpose |
|------|---------|
| `deploy --dry-run` | Full deploy path without submitting on-chain |
| `deploy --non-interactive --output json` | Machine-readable deploy output |

## CI

GitHub Actions job `e2e` expects repository secret `ACURAST_E2E_MNEMONIC`. Fork PRs without the secret skip the job with a clear message.

## Troubleshooting

- **Balance is 0**: fund the address via the canary faucet; preflight fails early with a direct link.
- **Deploy dry-run exits early**: deploy needs a funded wallet; mainnet configs with a 0 balance bail before the dry-run path, so scenarios use canary.
- **`expect(1)` not found**: install `expect`; required for the interactive scenarios.
- **Network errors**: container needs outbound HTTPS/WSS to Acurast endpoints.
8 changes: 8 additions & 0 deletions e2e/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
services:
e2e:
build:
context: ..
dockerfile: e2e/Dockerfile
environment:
ACURAST_E2E_MNEMONIC: ${ACURAST_E2E_MNEMONIC:-}
network_mode: bridge
Loading
Loading