Skip to content

Commit 49ce542

Browse files
committed
feat: nautilus logs — fetch and follow enclave logs via HTTP /logs endpoint with CLI command
1 parent 828cfa2 commit 49ce542

4 files changed

Lines changed: 158 additions & 2 deletions

File tree

docs/README.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Developer Machine EC2 Instance (Nitro-enabled)
3333
│ verify-signature │ │ │ NSM attestation │ │
3434
│ attest │ │ │ │ │
3535
│ status │ │ │ │ │
36+
│ logs │ │ │ │ │
3637
│ │ │ └────────────────────────────┘ │
3738
└─────────────────────┘ └──────────────────────────────────┘
3839
│ Sui RPC
@@ -55,6 +56,7 @@ Developer Machine EC2 Instance (Nitro-enabled)
5556
| Default HTTP port | 4000 | 3000 | 5000 |
5657
| Attestation endpoint | `GET /get_attestation` | `GET /attestation` | `GET /attestation` |
5758
| Sign endpoint | `POST /sign_name` (JSON body) | `POST /sign` (raw bytes) | `POST /sign` (raw bytes) |
59+
| Logs endpoint | `GET /logs?lines=N` | `GET /logs?lines=N` | `GET /logs?lines=N` |
5860
| VSOCK bridge | socat (systemd services) | argonaut host binary | socat (systemd services) |
5961
| Build | `docker build` + stagex `eif_build` | `make` (docker multi-stage, EIF built inside) | `make` (docker multi-stage, stagex `eif_build`) |
6062
| On-chain verify | `verify_signed_name` (BCS + IntentMessage) | `verify_signed_data` (blake2b256 + raw bytes) | `verify_signed_data` (blake2b256 + raw bytes) |
@@ -74,12 +76,13 @@ Developer Machine EC2 Instance (Nitro-enabled)
7476
nautilus-ops/
7577
├── nautilus-cli/ # CLI binary ("nautilus")
7678
│ └── src/
77-
│ ├── main.rs # Clap entry point — 10 subcommands
79+
│ ├── main.rs # Clap entry point — 11 subcommands
7880
│ ├── init.rs # nautilus init — scaffold project from template
7981
│ ├── build.rs # nautilus build — Docker + nitro-cli -> .eif + PCRs
8082
│ ├── init_ci.rs # nautilus init-ci — generates GitHub Actions workflow
8183
│ ├── status.rs # nautilus status — health, attestation & on-chain check
8284
│ ├── attest.rs # nautilus attest — fetch attestation + parse CBOR
85+
│ ├── logs.rs # nautilus logs — fetch/follow enclave logs
8386
│ ├── aws.rs # nautilus verify — EC2 enclave support check
8487
│ ├── sui_chain.rs # deploy-contract, register-enclave, update-pcrs, verify-signature
8588
│ └── config.rs # .nautilus.toml persistence, template detection
@@ -305,6 +308,18 @@ nautilus verify-signature \
305308
--data "Nautilus"
306309
```
307310

311+
**View Enclave Logs**
312+
313+
Fetch recent logs or follow them in real time:
314+
315+
```bash
316+
# Fetch the last 50 log lines
317+
nautilus logs --host <EC2_IP> -n 50
318+
319+
# Follow logs continuously (like tail -f)
320+
nautilus logs --host <EC2_IP> --follow
321+
```
322+
308323
**Check Status**
309324

310325
At any point, check the health of your entire stack:
@@ -329,6 +344,7 @@ After steps 1–6, any dApp on Sui can call `verify_signature()` or `verify_sign
329344
| `nautilus init` | Scaffold a new TEE project from a template (rust/ts/python) | git |
330345
| `nautilus build` | Build `.eif` from Dockerfile, extract PCR measurements | Docker |
331346
| `nautilus status` | Check enclave health, attestation, and on-chain PCR status | Enclave running |
347+
| `nautilus logs` | Fetch recent logs or follow live logs from a running enclave | Enclave running |
332348
| `nautilus init-ci` | Generate GitHub Actions deployment workflow ||
333349
| `nautilus attest` | Fetch attestation from enclave, parse CBOR, extract PCRs | Enclave running |
334350
| `nautilus verify` | Check if an EC2 instance supports Nitro Enclaves | `--features aws` |
@@ -462,7 +478,7 @@ cargo test -p nautilus-cli --features sui # includes on-chain config tes
462478

463479
| Repository | Description |
464480
|-----------|-------------|
465-
| [nautilus-rust](https://github.com/Ashwin-3cS/nautilus-rust/) | Rust TEE template — Axum sign-server powered by `nautilus-enclave`. Endpoints: `/sign_name`, `/get_attestation`, `/health` |
481+
| [nautilus-rust](https://github.com/Ashwin-3cS/nautilus-rust/) | Rust TEE template — Axum sign-server powered by `nautilus-enclave`. Endpoints: `/sign_name`, `/get_attestation`, `/health`, `/logs` |
466482
| [nautilus-ts](https://github.com/Ashwin-3cS/nautilus-ts/) | TypeScript TEE template — Bun + argonaut framework. Fork of [unconfirmedlabs/nautilus-ts](https://github.com/unconfirmedlabs/nautilus-ts). Endpoints: `/sign`, `/attestation`, `/health_check` |
467483
| [nautilus-python](https://github.com/Ashwin-3cS/nautilus-python/) | Python TEE template — stdlib HTTP server with pynacl Ed25519 and direct NSM ioctl. Endpoints: `/sign`, `/attestation`, `/health` |
468484

nautilus-cli/src/config.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ impl Template {
5454
}
5555
}
5656

57+
/// Logs endpoint path for this template.
58+
pub fn logs_path(self) -> &'static str {
59+
"/logs"
60+
}
61+
5762
/// GitHub repository name for this template.
5863
pub fn repo_name(self) -> &'static str {
5964
match self {

nautilus-cli/src/logs.rs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
use anyhow::{Context, Result};
2+
use clap::Args;
3+
use colored::Colorize;
4+
use std::io::{Read, Write};
5+
use std::net::TcpStream;
6+
use std::time::Duration;
7+
8+
use crate::config::{self, Template};
9+
10+
#[derive(Args, Debug)]
11+
pub struct LogsArgs {
12+
/// EC2 instance public hostname or IP.
13+
#[arg(long, env = "TEE_EC2_HOST")]
14+
pub host: String,
15+
16+
/// Override the default TCP port for the template.
17+
#[arg(long)]
18+
pub port: Option<u16>,
19+
20+
/// Number of recent log lines to fetch (default: 100, max: 1000).
21+
#[arg(long, short = 'n', default_value = "100")]
22+
pub lines: usize,
23+
24+
/// Poll for new logs continuously (every 2 seconds).
25+
#[arg(long, short = 'f')]
26+
pub follow: bool,
27+
}
28+
29+
pub async fn run(args: LogsArgs, cli_template: Option<Template>) -> Result<()> {
30+
let cfg = config::NautilusConfig::load(None).unwrap_or_default();
31+
let template = config::resolve_template(cli_template, &cfg)?;
32+
let port = args.port.unwrap_or(template.default_http_port());
33+
let path = template.logs_path();
34+
let n = args.lines.min(1000);
35+
36+
if args.follow {
37+
follow_logs(&args.host, port, path, n)?;
38+
} else {
39+
let lines = fetch_logs(&args.host, port, path, n)?;
40+
for line in &lines {
41+
println!("{}", line);
42+
}
43+
if lines.is_empty() {
44+
println!("{}", "No log lines available.".dimmed());
45+
}
46+
}
47+
48+
Ok(())
49+
}
50+
51+
/// Fetch log lines from the enclave's /logs endpoint.
52+
fn fetch_logs(host: &str, port: u16, path: &str, n: usize) -> Result<Vec<String>> {
53+
let url_path = format!("{}?lines={}", path, n);
54+
let addr = format!("{host}:{port}");
55+
56+
let mut stream = TcpStream::connect(&addr)
57+
.with_context(|| format!("Cannot reach enclave at {addr}"))?;
58+
59+
stream.set_read_timeout(Some(Duration::from_secs(5)))?;
60+
61+
let req = format!(
62+
"GET {url_path} HTTP/1.1\r\nHost: {host}:{port}\r\nConnection: close\r\n\r\n"
63+
);
64+
stream.write_all(req.as_bytes())?;
65+
66+
let mut response = Vec::new();
67+
stream.read_to_end(&mut response)?;
68+
let response_str = String::from_utf8_lossy(&response);
69+
70+
let body = response_str
71+
.split("\r\n\r\n")
72+
.nth(1)
73+
.context("Invalid HTTP response")?;
74+
75+
let json: serde_json::Value =
76+
serde_json::from_str(body).context("Failed to parse logs JSON")?;
77+
78+
let lines = json["lines"]
79+
.as_array()
80+
.context("Response missing 'lines' array")?
81+
.iter()
82+
.filter_map(|v| v.as_str().map(String::from))
83+
.collect();
84+
85+
Ok(lines)
86+
}
87+
88+
/// Continuously poll for new logs every 2 seconds.
89+
fn follow_logs(host: &str, port: u16, path: &str, initial_n: usize) -> Result<()> {
90+
println!(
91+
"{} Following logs from {}:{} (Ctrl+C to stop)",
92+
"→".cyan(),
93+
host,
94+
port
95+
);
96+
97+
let mut last_count = 0;
98+
99+
// First fetch: show initial_n lines
100+
match fetch_logs(host, port, path, initial_n) {
101+
Ok(lines) => {
102+
for line in &lines {
103+
println!("{}", line);
104+
}
105+
last_count = lines.len();
106+
}
107+
Err(e) => {
108+
eprintln!("{} {}", "✗".red(), e);
109+
}
110+
}
111+
112+
loop {
113+
std::thread::sleep(Duration::from_secs(2));
114+
115+
match fetch_logs(host, port, path, 1000) {
116+
Ok(lines) => {
117+
// Print only new lines (lines beyond what we last saw)
118+
if lines.len() > last_count {
119+
for line in &lines[last_count..] {
120+
println!("{}", line);
121+
}
122+
}
123+
last_count = lines.len();
124+
}
125+
Err(_) => {
126+
// Silently retry on connection errors in follow mode
127+
}
128+
}
129+
}
130+
}

nautilus-cli/src/main.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ mod init;
88
mod init_ci;
99
mod attest;
1010
mod status;
11+
mod logs;
1112
mod sui_chain;
1213

1314
/// Nautilus-Ops CLI — Self-Managed TEE Orchestrator for AWS Nitro Enclaves on Sui
@@ -44,6 +45,9 @@ enum Commands {
4445
/// Check enclave health, attestation, and on-chain status
4546
Status(status::StatusArgs),
4647

48+
/// Fetch recent logs from a running enclave
49+
Logs(logs::LogsArgs),
50+
4751
/// Verify that an EC2 instance has Nitro Enclave support enabled
4852
Verify(VerifyArgs),
4953

@@ -77,6 +81,7 @@ async fn main() -> Result<()> {
7781
Commands::InitCi(args) => init_ci::run(args, cli.template).await,
7882
Commands::Attest(args) => attest::run(args, cli.template).await,
7983
Commands::Status(args) => status::run(args, cli.template).await,
84+
Commands::Logs(args) => logs::run(args, cli.template).await,
8085
Commands::Verify(args) => aws::verify_enclave_enabled(&args.instance_id).await,
8186
Commands::DeployContract(args) => sui_chain::deploy_contract(args).await,
8287
Commands::RegisterEnclave(args) => sui_chain::register_enclave(args, cli.template).await,

0 commit comments

Comments
 (0)