From d1672e161347be6f6874ae91e346eff1d68ee4ca Mon Sep 17 00:00:00 2001 From: Jacob Dubin Date: Tue, 14 Apr 2026 20:36:07 -0500 Subject: [PATCH] updates for testing --- OpenJibo/docs/live-jibo-capture.md | 5 + OpenJibo/docs/live-jibo-test-runbook.md | 134 ++++++++++++++++++ .../bootstrap/test-openjibo-routing.sh | 16 +++ OpenJibo/scripts/cloud/README.md | 8 ++ .../cloud/get-websocket-capture-summary.sh | 48 +++++++ .../cloud/import-websocket-capture-fixture.py | 70 +++++++++ .../scripts/cloud/invoke-live-jibo-prep.sh | 37 +++++ .../cloud/start-dotnet-with-node-cert.sh | 67 +++++++++ 8 files changed, 385 insertions(+) create mode 100644 OpenJibo/docs/live-jibo-test-runbook.md create mode 100644 OpenJibo/scripts/bootstrap/test-openjibo-routing.sh create mode 100644 OpenJibo/scripts/cloud/get-websocket-capture-summary.sh create mode 100644 OpenJibo/scripts/cloud/import-websocket-capture-fixture.py create mode 100644 OpenJibo/scripts/cloud/invoke-live-jibo-prep.sh create mode 100644 OpenJibo/scripts/cloud/start-dotnet-with-node-cert.sh diff --git a/OpenJibo/docs/live-jibo-capture.md b/OpenJibo/docs/live-jibo-capture.md index 24ecce0..2449103 100644 --- a/OpenJibo/docs/live-jibo-capture.md +++ b/OpenJibo/docs/live-jibo-capture.md @@ -65,3 +65,8 @@ Useful helper scripts: - [scripts/cloud/Invoke-LiveJiboPrep.ps1](/OpenJibo/scripts/cloud/Invoke-LiveJiboPrep.ps1) - [scripts/cloud/Get-WebSocketCaptureSummary.ps1](/OpenJibo/scripts/cloud/Get-WebSocketCaptureSummary.ps1) - [scripts/cloud/Import-WebSocketCaptureFixture.ps1](/OpenJibo/scripts/cloud/Import-WebSocketCaptureFixture.ps1) +- [scripts/cloud/start-dotnet-with-node-cert.sh](/OpenJibo/scripts/cloud/start-dotnet-with-node-cert.sh) +- [scripts/cloud/invoke-live-jibo-prep.sh](/OpenJibo/scripts/cloud/invoke-live-jibo-prep.sh) +- [scripts/cloud/get-websocket-capture-summary.sh](/OpenJibo/scripts/cloud/get-websocket-capture-summary.sh) +- [scripts/cloud/import-websocket-capture-fixture.py](/OpenJibo/scripts/cloud/import-websocket-capture-fixture.py) +- [live-jibo-test-runbook.md](/OpenJibo/docs/live-jibo-test-runbook.md) diff --git a/OpenJibo/docs/live-jibo-test-runbook.md b/OpenJibo/docs/live-jibo-test-runbook.md new file mode 100644 index 0000000..d03b68e --- /dev/null +++ b/OpenJibo/docs/live-jibo-test-runbook.md @@ -0,0 +1,134 @@ +# Live Jibo .NET Test Runbook + +## Goal + +Run the first real `Jibo -> .NET OpenJibo cloud` test on the Ubuntu machine using the same working certificate and controlled routing that currently work with the Node server. + +This runbook intentionally avoids introducing Azure, new hostnames, or new robot bootstrap changes during the first live test. + +## Recommended Approach + +Use the existing Ubuntu networking path and certificate material first. + +- keep the current controlled Wi-Fi / routing arrangement +- keep the current Jibo-facing hostnames: + - `api.jibo.com` + - `api-socket.jibo.com` + - `neo-hub.jibo.com` +- keep the Node server available as a fallback +- run the `.NET` API with the same cert/key material by converting it to a temporary `.pfx` for Kestrel + +## Prerequisites On Ubuntu + +Install or confirm these tools: + +- `dotnet` +- `openssl` +- `curl` +- `python3` + +Optional but useful: + +- `pwsh` + +`pwsh` is not required anymore for the Ubuntu live test path if you use the bash/python helpers added here. + +## Certificate Plan + +The Node server currently uses: + +- `cert.pem` +- `key.pem` + +The `.NET` API can reuse that same material for the test by converting it at startup into a temporary `.pfx`. + +If your current cert file already includes the working chain, use it as-is. + +If your chain is separate, pass it as `CHAIN_PEM`. + +## Step By Step + +1. On Ubuntu, stop the Node server if it is currently bound to port `443`. + +2. From the repo root, start the `.NET` cloud using the same cert/key: + +```bash +./scripts/cloud/start-dotnet-with-node-cert.sh +``` + +Optional environment overrides: + +```bash +CERT_PEM=/path/to/cert.pem \ +KEY_PEM=/path/to/key.pem \ +CHAIN_PEM=/path/to/chain.pem \ +ASPNETCORE_URLS="https://0.0.0.0:443;http://0.0.0.0:24605" \ +./scripts/cloud/start-dotnet-with-node-cert.sh +``` + +3. In another terminal, run the prep checklist: + +```bash +./scripts/cloud/invoke-live-jibo-prep.sh +``` + +4. Verify controlled routing from the Ubuntu environment: + +```bash +./scripts/bootstrap/test-openjibo-routing.sh +``` + +5. Power on Jibo and let it connect using the existing controlled network configuration. + +6. Perform the first live checks in this order: + +- startup / bootstrap reachability +- one simple chat turn +- one joke turn + +7. After the run, summarize the captured websocket telemetry: + +```bash +./scripts/cloud/get-websocket-capture-summary.sh +``` + +8. Inspect exported fixtures under: + +- `src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Api/bin/Debug/net10.0/captures/websocket/fixtures/` + +9. Import the best fixture into the checked-in websocket fixture set: + +```bash +python3 ./scripts/cloud/import-websocket-capture-fixture.py \ + /path/to/exported.flow.json \ + neo-hub-real-jibo-first-chat +``` + +10. Keep notes on: + +- whether startup succeeded cleanly +- which websocket paths connected +- whether audio stayed pending or finalized +- whether EOS timing matched expectations +- whether any unexpected message families appeared + +## What To Do If The Test Fails + +If the robot does not connect or the first turn fails: + +1. confirm the `.NET` API is actually bound on `443` +2. confirm the cert presented by the `.NET` API matches the currently working Node cert path +3. confirm the Ubuntu routing still points Jibo traffic at the same machine +4. compare the `.NET` websocket capture output with prior Node logs +5. temporarily switch back to Node to confirm the environment still works + +## Not In Scope For This First Test + +Do not mix these into the first live run: + +- Azure deployment cutover +- new permanent OpenJibo hostnames +- IaC rollout +- new device bootstrap edits beyond the already working setup + +Those are valid next steps, but they should follow the first successful `.NET` live capture, not precede it. diff --git a/OpenJibo/scripts/bootstrap/test-openjibo-routing.sh b/OpenJibo/scripts/bootstrap/test-openjibo-routing.sh new file mode 100644 index 0000000..5111340 --- /dev/null +++ b/OpenJibo/scripts/bootstrap/test-openjibo-routing.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOSTS=( + "https://api.jibo.com/health" + "https://api-socket.jibo.com/" + "https://neo-hub.jibo.com/v1/proactive" +) + +for url in "${HOSTS[@]}"; do + if status_code="$(curl --silent --output /dev/null --write-out "%{http_code}" --insecure "${url}")"; then + echo "${url} status=${status_code} success=true" + else + echo "${url} status=000 success=false" + fi +done diff --git a/OpenJibo/scripts/cloud/README.md b/OpenJibo/scripts/cloud/README.md index deea33a..d6a5611 100644 --- a/OpenJibo/scripts/cloud/README.md +++ b/OpenJibo/scripts/cloud/README.md @@ -12,3 +12,11 @@ These scripts help exercise the new .NET hosted cloud locally. Runs a small readiness checklist before the first physical Jibo test against the .NET cloud. - `Import-WebSocketCaptureFixture.ps1` Sanitizes an exported websocket capture fixture and copies it into the checked-in websocket fixture set. +- `start-dotnet-with-node-cert.sh` + Starts the .NET API on Linux using the same PEM certificate material already used by the Node server. +- `invoke-live-jibo-prep.sh` + Bash equivalent of the live-run prep checklist for Ubuntu. +- `get-websocket-capture-summary.sh` + Bash summary helper for captured websocket telemetry and exported fixtures. +- `import-websocket-capture-fixture.py` + Cross-platform import/sanitization helper for exported websocket fixtures. diff --git a/OpenJibo/scripts/cloud/get-websocket-capture-summary.sh b/OpenJibo/scripts/cloud/get-websocket-capture-summary.sh new file mode 100644 index 0000000..9375c2e --- /dev/null +++ b/OpenJibo/scripts/cloud/get-websocket-capture-summary.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CAPTURE_DIRECTORY="${1:-${SCRIPT_DIR}/../../src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Api/bin/Debug/net10.0/captures/websocket}" + +if [[ ! -d "${CAPTURE_DIRECTORY}" ]]; then + echo "No websocket capture directory found at ${CAPTURE_DIRECTORY}" + exit 0 +fi + +shopt -s nullglob +event_files=( "${CAPTURE_DIRECTORY}"/*.events.ndjson ) +if [[ ${#event_files[@]} -eq 0 ]]; then + echo "No websocket telemetry event files found in ${CAPTURE_DIRECTORY}" + exit 0 +fi + +python3 - "$CAPTURE_DIRECTORY" "${event_files[@]}" <<'PY' +import collections +import json +import os +import sys + +capture_dir = sys.argv[1] +event_files = sys.argv[2:] + +counter = collections.Counter() +for path in event_files: + with open(path, "r", encoding="utf-8") as handle: + for line in handle: + line = line.strip() + if not line: + continue + payload = json.loads(line) + counter[payload.get("EventType", "unknown")] += 1 + +for key in sorted(counter): + print(f"{key}: {counter[key]}") + +fixture_dir = os.path.join(capture_dir, "fixtures") +if os.path.isdir(fixture_dir): + print("") + print("Exported websocket fixtures:") + for name in sorted(os.listdir(fixture_dir)): + if name.endswith(".flow.json"): + print(f" - {name}") +PY diff --git a/OpenJibo/scripts/cloud/import-websocket-capture-fixture.py b/OpenJibo/scripts/cloud/import-websocket-capture-fixture.py new file mode 100644 index 0000000..0654182 --- /dev/null +++ b/OpenJibo/scripts/cloud/import-websocket-capture-fixture.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +import argparse +import json +from pathlib import Path + + +def redact(value): + if value is None: + return None + + if isinstance(value, str): + lowered = value.lower() + if "token" in lowered or "bearer" in lowered or "session" in lowered: + return "[redacted]" + return value + + if isinstance(value, list): + return [redact(item) for item in value] + + if isinstance(value, dict): + result = {} + for key, item in value.items(): + lowered = key.lower() + if "token" in lowered or "authorization" in lowered: + result[key] = "[redacted]" + else: + result[key] = redact(item) + return result + + return value + + +def main(): + parser = argparse.ArgumentParser(description="Import and sanitize an exported websocket capture fixture.") + parser.add_argument("source_path") + parser.add_argument("fixture_name") + parser.add_argument( + "--destination-directory", + default=str(Path(__file__).resolve().parents[2] / "src" / "Jibo.Cloud" / "node" / "fixtures" / "websocket"), + ) + parser.add_argument("--overwrite", action="store_true") + args = parser.parse_args() + + source_path = Path(args.source_path).resolve() + destination_directory = Path(args.destination_directory).resolve() + destination_directory.mkdir(parents=True, exist_ok=True) + destination_path = destination_directory / f"{args.fixture_name}.flow.json" + + if destination_path.exists() and not args.overwrite: + raise SystemExit(f"Destination fixture already exists: {destination_path}. Use --overwrite to replace it.") + + with source_path.open("r", encoding="utf-8") as handle: + fixture = json.load(handle) + + sanitized = redact(fixture) + sanitized["name"] = args.fixture_name + if "session" in sanitized and isinstance(sanitized["session"], dict): + sanitized["session"]["token"] = "[redacted]" + + with destination_path.open("w", encoding="utf-8", newline="\n") as handle: + json.dump(sanitized, handle, indent=2) + handle.write("\n") + + print("Imported sanitized websocket fixture:") + print(f" - source: {source_path}") + print(f" - destination: {destination_path}") + + +if __name__ == "__main__": + main() diff --git a/OpenJibo/scripts/cloud/invoke-live-jibo-prep.sh b/OpenJibo/scripts/cloud/invoke-live-jibo-prep.sh new file mode 100644 index 0000000..91f61b4 --- /dev/null +++ b/OpenJibo/scripts/cloud/invoke-live-jibo-prep.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BASE_URL="${BASE_URL:-https://localhost:5001}" +CAPTURE_DIRECTORY="${CAPTURE_DIRECTORY:-${SCRIPT_DIR}/../../src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Api/bin/Debug/net10.0/captures/websocket}" +EXPECTED_HOSTS=( + "api.jibo.com" + "api-socket.jibo.com" + "neo-hub.jibo.com" +) + +echo "OpenJibo live Jibo prep" +echo "" + +echo "1. HTTP health check" +curl --silent --show-error --fail "${BASE_URL%/}/health" | python3 -m json.tool + +echo "" +echo "2. Expected robot-facing hosts" +for host in "${EXPECTED_HOSTS[@]}"; do + echo " - ${host}" +done + +echo "" +echo "3. Capture directory" +mkdir -p "${CAPTURE_DIRECTORY}" +echo " - ${CAPTURE_DIRECTORY}" + +echo "" +echo "4. Live-run checklist" +echo " - keep the Ubuntu/Jibo routing setup in place" +echo " - keep the Node server available as a fallback" +echo " - point Jibo at the .NET server using the same controlled network settings" +echo " - perform one startup check, one chat turn, and one joke turn" +echo " - after the run, inspect capture output with scripts/cloud/get-websocket-capture-summary.sh" +echo " - import the best exported fixture with scripts/cloud/import-websocket-capture-fixture.py" diff --git a/OpenJibo/scripts/cloud/start-dotnet-with-node-cert.sh b/OpenJibo/scripts/cloud/start-dotnet-with-node-cert.sh new file mode 100644 index 0000000..245d678 --- /dev/null +++ b/OpenJibo/scripts/cloud/start-dotnet-with-node-cert.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +API_PROJECT="${REPO_ROOT}/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Api/Jibo.Cloud.Api.csproj" + +CERT_PEM="${CERT_PEM:-${REPO_ROOT}/src/Jibo.Cloud/node/cert.pem}" +KEY_PEM="${KEY_PEM:-${REPO_ROOT}/src/Jibo.Cloud/node/key.pem}" +CHAIN_PEM="${CHAIN_PEM:-}" +PFX_OUT="${PFX_OUT:-${REPO_ROOT}/.tmp/openjibo-dev-cert.pfx}" +PFX_PASSWORD="${PFX_PASSWORD:-openjibo-dev-password}" +ASPNETCORE_URLS="${ASPNETCORE_URLS:-https://0.0.0.0:443;http://0.0.0.0:24605}" +DOTNET_ENVIRONMENT="${DOTNET_ENVIRONMENT:-Development}" + +mkdir -p "$(dirname "${PFX_OUT}")" + +if [[ ! -f "${CERT_PEM}" ]]; then + echo "Missing CERT_PEM: ${CERT_PEM}" >&2 + exit 1 +fi + +if [[ ! -f "${KEY_PEM}" ]]; then + echo "Missing KEY_PEM: ${KEY_PEM}" >&2 + exit 1 +fi + +OPENSSL_ARGS=( + pkcs12 + -export + -out "${PFX_OUT}" + -inkey "${KEY_PEM}" + -in "${CERT_PEM}" + -passout "pass:${PFX_PASSWORD}" +) + +if [[ -n "${CHAIN_PEM}" ]]; then + if [[ ! -f "${CHAIN_PEM}" ]]; then + echo "Missing CHAIN_PEM: ${CHAIN_PEM}" >&2 + exit 1 + fi + + OPENSSL_ARGS+=( -certfile "${CHAIN_PEM}" ) +fi + +echo "Creating PFX for Kestrel" +echo " - cert: ${CERT_PEM}" +echo " - key: ${KEY_PEM}" +if [[ -n "${CHAIN_PEM}" ]]; then + echo " - chain: ${CHAIN_PEM}" +fi +echo " - pfx: ${PFX_OUT}" +openssl "${OPENSSL_ARGS[@]}" + +export ASPNETCORE_URLS +export DOTNET_ENVIRONMENT +export ASPNETCORE_Kestrel__Certificates__Default__Path="${PFX_OUT}" +export ASPNETCORE_Kestrel__Certificates__Default__Password="${PFX_PASSWORD}" + +echo "" +echo "Starting OpenJibo .NET cloud" +echo " - project: ${API_PROJECT}" +echo " - urls: ${ASPNETCORE_URLS}" +echo " - environment: ${DOTNET_ENVIRONMENT}" + +cd "${REPO_ROOT}" +exec dotnet run --project "${API_PROJECT}" --no-launch-profile