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
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ dev = ["ruff>=0.8.0", "ty>=0.0.1a1"]
[project.scripts]
seismic-deploy = "deploy_gcp.seismic_deploy.cli:cli"
# Operator-facing: act on your own node. Cloud-agnostic, never wraps Pulumi.
seismic-tee = "tee.cli:app"
seismic-tee = "tee.cli.node.app:app"
# Seismic-internal: bootstrap a network (provision cohort + genesis ceremony).
seismic-tee-bootstrap = "tee.bootstrap_cli:app"
seismic-tee-bootstrap = "tee.cli.network.app:app"

[tool.setuptools.packages.find]
include = ["deploy_gcp*", "tee*"]
Expand Down
23 changes: 12 additions & 11 deletions tee/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ only consumes one to reach an already-running node.
Only `seismic-tee` is cloud-agnostic and **never wraps Pulumi**;
`seismic-tee-bootstrap` is the one allowed to. A **node descriptor** is a
small JSON (`public_ip`/`fqdn`) describing one provisioned node — see
`tee/descriptor.py`. It's what `seismic-tee-bootstrap up` emits
`tee/cli/common/descriptor.py`. It's what `seismic-tee-bootstrap up` emits
(just `{public_ip, fqdn}`); a BYO-infra operator can hand-write it (or use
`pulumi stack output --json` — only those two keys are read).

Expand All @@ -95,16 +95,17 @@ the same audience line:
— founding a *network* (orchestrating a cohort, the genesis ceremony),
which only Seismic does.

The current layout already anticipates this: the operator path
(`cli.py` → `configure.py` → `descriptor.py`) has no dependency on the
founding side and never imports Pulumi, so it lifts out cleanly, and the
dependency direction is one-way (private builds on public). The seam is
the node descriptor. At extraction the shared bits (`descriptor.py`,
`cli_common.py`) go to the public side and this repo depends on them.
The package layout mirrors this split: `cli/node/` + `cli/common/` +
`pulumi/seismic_node/` are the public half, `cli/network/` +
`pulumi/playground/` the private half. The dependency direction is
one-way (private builds on public): `cli/common/` imports neither side,
`cli/node/` imports only `cli/common/`, and only `cli/network/` may
import both. The seam is the node descriptor. At extraction the public
half lifts out wholesale and this repo depends on it.

Not done yet — **don't pre-split within this repo.** The rule to keep
honoring until then: the operator CLI stays cloud-agnostic and never wraps
Pulumi, and a new file goes on whichever side it would ultimately land.
The rules to keep honoring until then: the operator CLI stays
cloud-agnostic and never wraps Pulumi, the import direction above holds,
and a new file goes on whichever side it would ultimately land.

### Joining an existing network (the common case)

Expand Down Expand Up @@ -284,7 +285,7 @@ only gates against a second POST within the same boot.
today — it only POSTs the config. The `--measurements` flag is gated off and
errors if passed, rather than pretending to verify.

Why: the old path (`proxy.py`, Flashbots' `cvm-reverse-proxy`
Why: the old path (`cli/node/proxy.py`, Flashbots' `cvm-reverse-proxy`
`proxy-client` against `:7936`) is non-functional — no seismic node
serves that RA-TLS endpoint, and cvm-reverse-proxy's pre-handshake aTLS
is a different protocol than the `flashbots/attested-tls` the enclave
Expand Down
8 changes: 8 additions & 0 deletions tee/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""The two TEE CLIs and their shared code.

Import direction — it keeps the eventual public/private repo split
(see tee/README.md) mechanical: common/ imports nothing else here,
node/ imports only common/, network/ may import both. node/ and
common/ are slated for the public operator repo, so nothing they
import may come from network/.
"""
1 change: 1 addition & 0 deletions tee/cli/common/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Code shared by both CLIs. Must not import from ..node or ..network."""
File renamed without changes.
File renamed without changes.
12 changes: 7 additions & 5 deletions tee/manifest.py → tee/cli/common/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@
only command that takes loose files — then `assemble`/`validate` operate on
the directory):

uv run python -m tee.manifest init tee/networks/seismic-devnet-3 \
uv run python -m tee.cli.common.manifest init tee/networks/seismic-devnet-3 \
--reth-genesis dev.json \
--measurements ../seismic-images/build/measurements.json \
--measurement-id seismic_2026-06-11.abc123.vhd
# edit tee/networks/seismic-devnet-3/summit-template.toml, then:
uv run python -m tee.manifest assemble tee/networks/seismic-devnet-3
uv run python -m tee.manifest validate tee/networks/seismic-devnet-3
uv run python -m tee.cli.common.manifest assemble tee/networks/seismic-devnet-3
uv run python -m tee.cli.common.manifest validate tee/networks/seismic-devnet-3
"""

import argparse
Expand All @@ -45,7 +45,7 @@
from pathlib import Path
from typing import Any

from tee.utils.logging_setup import setup_logging
from tee.cli.common.logging_setup import setup_logging

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -777,7 +777,9 @@ def validate_reth_genesis_matches(


def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(prog="python -m tee.manifest", description=__doc__)
parser = argparse.ArgumentParser(
prog="python -m tee.cli.common.manifest", description=__doc__
)
sub = parser.add_subparsers(dest="command", required=True)

def add_reth_bin(p: argparse.ArgumentParser) -> None:
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
import urllib.request
from pathlib import Path

from tee import manifest as manifest_mod
from tee.manifest import (
from tee.cli.common import manifest as manifest_mod
from tee.cli.common.manifest import (
AssembledManifest,
GateContext,
GateError,
Expand Down
4 changes: 4 additions & 0 deletions tee/cli/network/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""`seismic-tee-bootstrap` — internal CLI to found a network.

May import ..node and ..common.
"""
16 changes: 8 additions & 8 deletions tee/bootstrap_cli.py → tee/cli/network/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
Pulumi —
`up` / `down` drive the seismic_node Automation-API orchestrator. The
operator CLI (`seismic-tee`) deliberately is not; the boundary is the node
descriptor file (see tee/descriptor.py), which this CLI produces
descriptor file (see tee/cli/common/descriptor.py), which this CLI produces
(via provisioning) and consumes (during the genesis ceremony).

Wired via [project.scripts] in pyproject.toml. Each leaf forwards its argv
to that module's argparse `main()`; see tee/cli_common.py.
to that module's argparse `main()`; see tee/cli/common/plumbing.py.
"""

import click

from tee.cli_common import PASSTHROUGH, forward
from tee.cli.common.plumbing import PASSTHROUGH, forward


@click.group()
Expand All @@ -27,7 +27,7 @@ def app() -> None:
@click.argument("argv", nargs=-1, type=click.UNPROCESSED)
def up(argv: tuple[str, ...]) -> None:
"""Provision a cohort of TDX nodes (one independent Pulumi stack each)."""
from tee import orchestrator
from tee.cli.network import orchestrator

forward(orchestrator.up_main, "seismic-tee-bootstrap up", argv)

Expand All @@ -36,7 +36,7 @@ def up(argv: tuple[str, ...]) -> None:
@click.argument("argv", nargs=-1, type=click.UNPROCESSED)
def down(argv: tuple[str, ...]) -> None:
"""Tear down cohort node(s); each stack destroys independently."""
from tee import orchestrator
from tee.cli.network import orchestrator

forward(orchestrator.down_main, "seismic-tee-bootstrap down", argv)

Expand All @@ -45,7 +45,7 @@ def down(argv: tuple[str, ...]) -> None:
@click.argument("argv", nargs=-1, type=click.UNPROCESSED)
def configure(argv: tuple[str, ...]) -> None:
"""Configure a cohort in parallel: one genesis + N joiners, one command."""
from tee import cohort_configure
from tee.cli.network import cohort_configure

forward(cohort_configure.main, "seismic-tee-bootstrap configure", argv)

Expand All @@ -56,7 +56,7 @@ def configure(argv: tuple[str, ...]) -> None:
@click.argument("argv", nargs=-1, type=click.UNPROCESSED)
def genesis_ceremony(argv: tuple[str, ...]) -> None:
"""One-shot genesis ceremony: build genesis.toml from the cohort, fan it out."""
from tee import genesis as genesis_mod
from tee.cli.network import genesis as genesis_mod

forward(genesis_mod.main, "seismic-tee-bootstrap genesis-ceremony", argv)

Expand All @@ -65,7 +65,7 @@ def genesis_ceremony(argv: tuple[str, ...]) -> None:
@click.argument("argv", nargs=-1, type=click.UNPROCESSED)
def manifest(argv: tuple[str, ...]) -> None:
"""Scaffold (init) / assemble / validate a network artifact-set dir."""
from tee import manifest as manifest_mod
from tee.cli.common import manifest as manifest_mod

forward(manifest_mod.main, "seismic-tee-bootstrap manifest", argv)

Expand Down
10 changes: 5 additions & 5 deletions tee/cohort_configure.py → tee/cli/network/cohort_configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,17 @@
from dataclasses import dataclass, field
from pathlib import Path

from tee import manifest as manifest_mod
from tee.configure import (
from tee.cli.common import manifest as manifest_mod
from tee.cli.common.descriptor import load_descriptor, require
from tee.cli.common.logging_setup import setup_logging
from tee.cli.node.configure import (
ENCLAVE_PEER_PORT,
TDX_INIT_PORT,
build_config,
post_config_to_tdx_init,
resolve_reth_genesis,
)
from tee.descriptor import load_descriptor, require
from tee.status import poll_provisioning
from tee.utils.logging_setup import setup_logging
from tee.cli.node.status import poll_provisioning

logger = logging.getLogger(__name__)

Expand Down
10 changes: 5 additions & 5 deletions tee/genesis.py → tee/cli/network/genesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
open → summit keygen, and both are polled until every node responds — so
it can run straight after `configure` without hand-timing the boot tail.

Each node is located by a descriptor file (see tee/descriptor.py),
Each node is located by a descriptor file (see tee/cli/common/descriptor.py),
so this never calls Pulumi.
"""

Expand All @@ -33,9 +33,9 @@

import requests

from tee import manifest as manifest_mod
from tee.descriptor import load_descriptor, require
from tee.utils.summit_client import PublicKeys, SummitClient
from tee.cli.common import manifest as manifest_mod
from tee.cli.common.descriptor import load_descriptor, require
from tee.cli.network.summit_client import PublicKeys, SummitClient

# Summit's consensus (BLS) port. Each validator entry in the generated
# genesis.toml pins "<ip>:<CONSENSUS_PORT>".
Expand Down Expand Up @@ -171,7 +171,7 @@ def _get_pubkeys(
"""Gather each cohort node's summit pubkeys from its descriptor file.

Resolves every node's fqdn/public_ip from its descriptor (see
tee/descriptor.py), so the summit endpoints come from whatever
tee/cli/common/descriptor.py), so the summit endpoints come from whatever
infra tool produced the descriptor — this script never calls Pulumi.
Returns the validator entries and the per-node summit clients (reused
for the genesis.toml fanout).
Expand Down
6 changes: 3 additions & 3 deletions tee/orchestrator.py → tee/cli/network/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
of the single-node `seismic_node` program via the Pulumi Automation API
(local-program workspace), and writes one node descriptor per node (the
same `{public_ip, fqdn, …}` shape `pulumi stack output --json` emits — see
tee/descriptor.py).
tee/cli/common/descriptor.py).

Shared settings are inherited from an existing stack config file
(`Pulumi.dev.yaml` by default): the orchestrator reads its `config:` block
Expand Down Expand Up @@ -49,12 +49,12 @@
import yaml
from pulumi import automation as auto

from tee import manifest as manifest_mod
from tee.cli.common import manifest as manifest_mod

# The single-node program this orchestrator fans out over. A local-program
# workspace points at this dir, so the project name / runtime / venv all
# come from its Pulumi.yaml — identical to running `pulumi` in that dir.
SEISMIC_NODE_DIR = Path(__file__).parent / "pulumi" / "seismic_node"
SEISMIC_NODE_DIR = Path(__file__).parents[2] / "pulumi" / "seismic_node"

# Shared settings are inherited from this stack config unless --config overrides.
DEFAULT_CONFIG = SEISMIC_NODE_DIR / "Pulumi.dev.yaml"
Expand Down
File renamed without changes.
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
import unittest
from pathlib import Path

from tee.cohort_configure import build_cohort
from tee.configure import ENCLAVE_PEER_PORT
from tee.cli.network.cohort_configure import build_cohort
from tee.cli.node.configure import ENCLAVE_PEER_PORT


def _descriptor(name: str, public_ip: str, fqdn: str) -> Path:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
from pathlib import Path
from unittest import mock

from tee import genesis
from tee.utils.summit_client import PublicKeys
from tee.cli.network import genesis
from tee.cli.network.summit_client import PublicKeys


class ParseArgsTests(unittest.TestCase):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
import unittest
from pathlib import Path

from tee.manifest import render_manifest
from tee.orchestrator import (
from tee.cli.common.manifest import render_manifest
from tee.cli.network.orchestrator import (
DEFAULT_OUT_DIR,
_check_vhd_matches_network,
_resolve_out_dir,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Tests for tee.utils.summit_client (stdlib unittest; no test deps).
"""Tests for tee.cli.network.summit_client (stdlib unittest; no test deps).

Run with:
uv run python -m unittest discover -s tee/tests -v
Expand All @@ -7,8 +7,8 @@
import unittest
from unittest import mock

from tee.utils import summit_client
from tee.utils.summit_client import SummitClient
from tee.cli.network import summit_client
from tee.cli.network.summit_client import SummitClient


class SummitClientTests(unittest.TestCase):
Expand Down
1 change: 1 addition & 0 deletions tee/cli/node/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""`seismic-tee` — operator CLI for a single node. Imports only ..common."""
8 changes: 4 additions & 4 deletions tee/cli.py → tee/cli/node/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
lives in the separate `seismic-tee-bootstrap` CLI.

Wired via [project.scripts] in pyproject.toml. Each leaf forwards its argv
to that module's argparse `main()`; see tee/cli_common.py.
to that module's argparse `main()`; see tee/cli/common/plumbing.py.
"""

import click

from tee.cli_common import PASSTHROUGH, forward
from tee.cli.common.plumbing import PASSTHROUGH, forward


@click.group()
Expand All @@ -24,7 +24,7 @@ def app() -> None:
@click.argument("argv", nargs=-1, type=click.UNPROCESSED)
def configure(argv: tuple[str, ...]) -> None:
"""Configure a node to join a network: assemble + POST config to tdx-init."""
from tee import configure as configure_mod
from tee.cli.node import configure as configure_mod

forward(configure_mod.main, "seismic-tee configure", argv)

Expand All @@ -33,7 +33,7 @@ def configure(argv: tuple[str, ...]) -> None:
@click.argument("argv", nargs=-1, type=click.UNPROCESSED)
def status(argv: tuple[str, ...]) -> None:
"""Watch a node's first-boot LUKS provisioning progress."""
from tee import status as status_mod
from tee.cli.node import status as status_mod

forward(status_mod.main, "seismic-tee status", argv)

Expand Down
12 changes: 6 additions & 6 deletions tee/configure.py → tee/cli/node/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from flags, `[domain]` from the descriptor fqdn + `--email`, and `[network]`
from `--manifest` + `--reth-genesis`. Those network-wide artifacts stay
standalone files, merged only at POST time. The node address is *brought by
the operator* via a descriptor file (see tee/descriptor.py), typically
the operator* via a descriptor file (see tee/cli/common/descriptor.py), typically
`pulumi stack output --json`. The CLI never provisions infrastructure
(Pulumi's job).

Expand All @@ -34,11 +34,11 @@

import requests

from tee import manifest as manifest_mod
from tee.descriptor import load_descriptor, require
from tee.proxy import ProxyClient
from tee.status import watch_luks_provisioning
from tee.utils.logging_setup import setup_logging
from tee.cli.common import manifest as manifest_mod
from tee.cli.common.descriptor import load_descriptor, require
from tee.cli.common.logging_setup import setup_logging
from tee.cli.node.proxy import ProxyClient
from tee.cli.node.status import watch_luks_provisioning

logger = logging.getLogger(__name__)

Expand Down
File renamed without changes.
7 changes: 4 additions & 3 deletions tee/status.py → tee/cli/node/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@

import requests

from tee.descriptor import load_descriptor, require
from tee.utils.logging_setup import setup_logging
from tee.cli.common.descriptor import load_descriptor, require
from tee.cli.common.logging_setup import setup_logging

ENCLAVE_PORT = 7878
POLL_INTERVAL_SECONDS = 5
Expand Down Expand Up @@ -292,7 +292,8 @@ def parse_args() -> argparse.Namespace:
type=Path,
required=True,
metavar="DESCRIPTOR",
help="Node descriptor JSON (provides public_ip); see tee/descriptor.py.",
help="Node descriptor JSON (provides public_ip); "
"see tee/cli/common/descriptor.py.",
)
parser.add_argument(
"--once",
Expand Down
Empty file added tee/cli/node/tests/__init__.py
Empty file.
Loading
Loading