426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
230 lines
7.6 KiB
Python
230 lines
7.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Cross-backend cloud orchestration CLI.
|
|
|
|
Read-only verification harness for the ``BackendAdapter`` Protocol.
|
|
Dispatches by ``--backend`` through ``BACKEND_REGISTRY``; new backends
|
|
become available the moment they call ``@register_backend(...)``.
|
|
|
|
Subcommands (no provisioning yet — that lands in a follow-up):
|
|
|
|
cloud_run.py search --backend vast --gpu-target b200-2x
|
|
cloud_run.py status --backend vast --instance-id 12345678
|
|
cloud_run.py teardown --backend vast --instance-id 12345678 --yes
|
|
|
|
Existing entry points (``scripts/train_vast.sh``) are unaffected.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import importlib
|
|
import json
|
|
import logging
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
from typing import cast
|
|
|
|
# Make ``scripts.lib.backends.*`` importable when this file is run as a
|
|
# script (``python3 scripts/cloud_run.py ...``) — the repo root must be
|
|
# on sys.path. When invoked as ``python3 -m scripts.cloud_run`` this is
|
|
# already true, so the guarded insert leaves sys.path unchanged.
|
|
_REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
if str(_REPO_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(_REPO_ROOT))
|
|
|
|
from scripts.lib.backends.base import ( # noqa: E402 (after sys.path tweak)
|
|
BACKEND_REGISTRY,
|
|
BackendAdapter,
|
|
BackendError,
|
|
InstanceHandle,
|
|
NoOffersError,
|
|
OfferConstraints,
|
|
)
|
|
|
|
|
|
# Importing each backend module triggers its ``@register_backend`` side
|
|
# effect. Add new backends here as they land.
|
|
_BACKEND_MODULES = (
|
|
"scripts.lib.backends.vast",
|
|
)
|
|
|
|
|
|
def _load_backend(name: str) -> BackendAdapter:
|
|
for mod in _BACKEND_MODULES:
|
|
importlib.import_module(mod)
|
|
if name not in BACKEND_REGISTRY:
|
|
raise SystemExit(
|
|
f"unknown backend {name!r}; registered: {sorted(BACKEND_REGISTRY)}"
|
|
)
|
|
cls = BACKEND_REGISTRY[name]
|
|
return cast(BackendAdapter, cls())
|
|
|
|
|
|
def _cmd_search(args: argparse.Namespace) -> int:
|
|
backend = _load_backend(args.backend)
|
|
constraints = OfferConstraints(
|
|
gpu_target=args.gpu_target,
|
|
min_disk_gb=args.min_disk_gb,
|
|
min_inet_down_mbps=args.min_inet_down_mbps,
|
|
min_reliability=args.min_reliability,
|
|
min_duration_days=args.min_duration_days,
|
|
max_dph=args.max_dph,
|
|
)
|
|
try:
|
|
offers = backend.search_offers(constraints)
|
|
except NoOffersError as e:
|
|
# No offers is a normal "empty result" — exit 0 with a message,
|
|
# not 1, so shell pipelines can grep without tripping `set -e`.
|
|
print(f"[cloud_run] no offers: {e}", file=sys.stderr)
|
|
if args.json:
|
|
print("[]")
|
|
return 0
|
|
except BackendError as e:
|
|
print(f"[cloud_run] backend error: {e}", file=sys.stderr)
|
|
return 1
|
|
|
|
if args.json:
|
|
print(json.dumps(
|
|
[
|
|
{
|
|
"backend": o.backend,
|
|
"id": o.id,
|
|
"gpu_name": o.gpu_name,
|
|
"num_gpus": o.num_gpus,
|
|
"gpu_total_ram_gb": o.gpu_total_ram_gb,
|
|
"dph": o.dph,
|
|
"reliability": o.reliability,
|
|
"inet_down_mbps": o.inet_down_mbps,
|
|
"disk_space_gb": o.disk_space_gb,
|
|
"geolocation": o.geolocation,
|
|
}
|
|
for o in offers[: args.limit]
|
|
],
|
|
indent=2,
|
|
))
|
|
return 0
|
|
|
|
header = (
|
|
f"{'id':>10} {'gpu':<22} x {'$/hr':>6} "
|
|
f"{'rel':>5} {'down Mb':>8} {'disk GB':>8} geolocation"
|
|
)
|
|
print(f"[cloud_run] backend={backend.name} target={args.gpu_target} "
|
|
f"top {min(args.limit, len(offers))} of {len(offers)} offers:")
|
|
print(header)
|
|
print("-" * len(header))
|
|
for o in offers[: args.limit]:
|
|
print(
|
|
f"{o.id:>10} {o.gpu_name:<22} "
|
|
f"{o.num_gpus:<1} ${o.dph:>5.2f} "
|
|
f"{o.reliability:>5.3f} {o.inet_down_mbps:>8.0f} "
|
|
f"{o.disk_space_gb:>8.0f} {o.geolocation}"
|
|
)
|
|
return 0
|
|
|
|
|
|
def _cmd_status(args: argparse.Namespace) -> int:
|
|
backend = _load_backend(args.backend)
|
|
handle = InstanceHandle(
|
|
backend=backend.name,
|
|
instance_id=args.instance_id,
|
|
label=args.label or "",
|
|
created_at=time.time(),
|
|
)
|
|
try:
|
|
st = backend.status(handle)
|
|
except BackendError as e:
|
|
print(f"[cloud_run] backend error: {e}", file=sys.stderr)
|
|
return 1
|
|
|
|
if args.json:
|
|
print(json.dumps({
|
|
"backend": handle.backend,
|
|
"instance_id": handle.instance_id,
|
|
"state": st.state,
|
|
"gpu_name": st.gpu_name,
|
|
"num_gpus": st.num_gpus,
|
|
"uptime_s": st.uptime_s,
|
|
"public_endpoint": st.public_endpoint,
|
|
}, indent=2))
|
|
return 0
|
|
|
|
print(
|
|
f"[cloud_run] {backend.name}/{handle.instance_id}: "
|
|
f"state={st.state} gpu={st.gpu_name}x{st.num_gpus} "
|
|
f"uptime={int(st.uptime_s) if st.uptime_s is not None else '?'}s "
|
|
f"endpoint={st.public_endpoint or '<none>'}"
|
|
)
|
|
return 0
|
|
|
|
|
|
def _cmd_teardown(args: argparse.Namespace) -> int:
|
|
if not args.yes:
|
|
print(
|
|
"[cloud_run] teardown refuses to run without --yes "
|
|
"(this is destructive and bills accrue until destruction).",
|
|
file=sys.stderr,
|
|
)
|
|
return 2
|
|
backend = _load_backend(args.backend)
|
|
handle = InstanceHandle(
|
|
backend=backend.name,
|
|
instance_id=args.instance_id,
|
|
label=args.label or "",
|
|
created_at=time.time(),
|
|
)
|
|
try:
|
|
backend.teardown(handle, force=args.force)
|
|
except BackendError as e:
|
|
print(f"[cloud_run] backend error: {e}", file=sys.stderr)
|
|
return 1
|
|
print(f"[cloud_run] {backend.name}/{handle.instance_id}: destroyed")
|
|
return 0
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format="[cloud_run] %(message)s",
|
|
)
|
|
parser = argparse.ArgumentParser(prog="cloud_run.py")
|
|
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
|
|
p_search = sub.add_parser("search", help="list matching offers")
|
|
p_search.add_argument("--backend", required=True)
|
|
p_search.add_argument("--gpu-target", required=True)
|
|
p_search.add_argument("--min-disk-gb", type=int, default=500)
|
|
p_search.add_argument("--min-inet-down-mbps", type=int, default=500)
|
|
p_search.add_argument("--min-reliability", type=float, default=0.97)
|
|
p_search.add_argument("--min-duration-days", type=float, default=3.0)
|
|
p_search.add_argument("--max-dph", type=float, default=None)
|
|
p_search.add_argument("--limit", type=int, default=12)
|
|
p_search.add_argument("--json", action="store_true")
|
|
|
|
p_status = sub.add_parser("status", help="show one instance's status")
|
|
p_status.add_argument("--backend", required=True)
|
|
p_status.add_argument("--instance-id", required=True)
|
|
p_status.add_argument("--label", default=None)
|
|
p_status.add_argument("--json", action="store_true")
|
|
|
|
p_td = sub.add_parser("teardown", help="destroy an instance")
|
|
p_td.add_argument("--backend", required=True)
|
|
p_td.add_argument("--instance-id", required=True)
|
|
p_td.add_argument("--label", default=None)
|
|
p_td.add_argument("--yes", action="store_true",
|
|
help="confirm destruction (required)")
|
|
p_td.add_argument("--force", action="store_true")
|
|
|
|
args = parser.parse_args(argv)
|
|
if args.cmd == "search":
|
|
return _cmd_search(args)
|
|
if args.cmd == "status":
|
|
return _cmd_status(args)
|
|
if args.cmd == "teardown":
|
|
return _cmd_teardown(args)
|
|
return 2
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|