mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-25 06:42:52 +08:00
* feat(gateway): add installation name configuration Add a first-class operator-assigned gateway name with TOML, CLI, environment, and Helm configuration surfaces. Local gateways default to openshell, while Helm defaults to the chart fullname; operators sharing a collector across namespaces or clusters can set a globally distinct name. Signed-off-by: Kris Hicks <khicks@nvidia.com> * feat(gateway): identify gateways in exported traces Attach the configured gateway installation name and compute driver to the gateway OpenTelemetry resource so operators can filter traces from multiple installations that share a collector. Forward the gateway name and OTLP endpoint to managed external drivers so their distinct service resources carry the same installation identity. Keep service.name stable per process type, omit blank resource values, and leave per-span operation names and request attributes unchanged. Refs #2507 Signed-off-by: Kris Hicks <khicks@nvidia.com> --------- Signed-off-by: Kris Hicks <khicks@nvidia.com>
248 lines
7.9 KiB
Bash
248 lines
7.9 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
# Start a standalone openshell-gateway backed by the Docker compute driver for
|
|
# local manual testing.
|
|
#
|
|
# Defaults:
|
|
# - Plaintext HTTP on 127.0.0.1:18080
|
|
# - Dedicated sandbox namespace "docker-dev"
|
|
# - Persistent state under .cache/gateway-docker
|
|
#
|
|
# Common overrides:
|
|
# OPENSHELL_SERVER_PORT=19080 mise run gateway:docker
|
|
# OPENSHELL_DOCKER_GATEWAY_NAME=my-docker-gateway mise run gateway:docker
|
|
# OPENSHELL_SANDBOX_NAMESPACE=my-ns mise run gateway:docker
|
|
# OPENSHELL_SANDBOX_IMAGE=ghcr.io/... mise run gateway:docker
|
|
#
|
|
# After the gateway is running, point the CLI at it with either:
|
|
# openshell --gateway docker-dev <command>
|
|
# openshell gateway use docker-dev # then plain `openshell <command>`
|
|
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
PORT="${OPENSHELL_SERVER_PORT:-18080}"
|
|
GATEWAY_NAME="${OPENSHELL_DOCKER_GATEWAY_NAME:-docker-dev}"
|
|
STATE_DIR="${OPENSHELL_DOCKER_GATEWAY_STATE_DIR:-${ROOT}/.cache/gateway-docker}"
|
|
SANDBOX_NAMESPACE="${OPENSHELL_SANDBOX_NAMESPACE:-docker-dev}"
|
|
SANDBOX_IMAGE="${OPENSHELL_SANDBOX_IMAGE:-ghcr.io/nvidia/openshell-community/sandboxes/base:latest}"
|
|
SANDBOX_IMAGE_PULL_POLICY="${OPENSHELL_SANDBOX_IMAGE_PULL_POLICY:-IfNotPresent}"
|
|
LOG_LEVEL="${OPENSHELL_LOG_LEVEL:-info}"
|
|
GATEWAY_BIN="${ROOT}/target/debug/openshell-gateway"
|
|
|
|
normalize_arch() {
|
|
case "$1" in
|
|
x86_64|amd64) echo "amd64" ;;
|
|
aarch64|arm64) echo "arm64" ;;
|
|
*) echo "$1" ;;
|
|
esac
|
|
}
|
|
|
|
linux_target_triple() {
|
|
case "$1" in
|
|
amd64) echo "x86_64-unknown-linux-gnu" ;;
|
|
arm64) echo "aarch64-unknown-linux-gnu" ;;
|
|
*)
|
|
echo "ERROR: unsupported Docker daemon architecture '$1'" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
}
|
|
|
|
port_is_in_use() {
|
|
local port=$1
|
|
if command -v lsof >/dev/null 2>&1; then
|
|
lsof -nP -iTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1
|
|
return $?
|
|
fi
|
|
if command -v nc >/dev/null 2>&1; then
|
|
nc -z 127.0.0.1 "${port}" >/dev/null 2>&1
|
|
return $?
|
|
fi
|
|
(echo >/dev/tcp/127.0.0.1/"${port}") >/dev/null 2>&1
|
|
}
|
|
|
|
register_gateway_metadata() {
|
|
local name=$1
|
|
local endpoint=$2
|
|
local port=$3
|
|
local config_home gateway_dir
|
|
|
|
config_home="${XDG_CONFIG_HOME:-${HOME}/.config}"
|
|
gateway_dir="${config_home}/openshell/gateways/${name}"
|
|
|
|
mkdir -p "${gateway_dir}"
|
|
cat >"${gateway_dir}/metadata.json" <<EOF
|
|
{
|
|
"name": "${name}",
|
|
"gateway_endpoint": "${endpoint}",
|
|
"is_remote": false,
|
|
"gateway_port": ${port},
|
|
"auth_mode": "plaintext"
|
|
}
|
|
EOF
|
|
}
|
|
|
|
if [[ ! "${GATEWAY_NAME}" =~ ^[A-Za-z0-9._-]+$ ]]; then
|
|
echo "ERROR: OPENSHELL_DOCKER_GATEWAY_NAME must contain only letters, numbers, dots, underscores, or dashes" >&2
|
|
exit 2
|
|
fi
|
|
|
|
if ! command -v docker >/dev/null 2>&1; then
|
|
echo "ERROR: docker CLI is required" >&2
|
|
exit 2
|
|
fi
|
|
if ! docker info >/dev/null 2>&1; then
|
|
echo "ERROR: docker daemon is not reachable" >&2
|
|
exit 2
|
|
fi
|
|
|
|
if port_is_in_use "${PORT}"; then
|
|
echo "ERROR: port ${PORT} is already in use; free it or set OPENSHELL_SERVER_PORT" >&2
|
|
exit 2
|
|
fi
|
|
|
|
GRPC_ENDPOINT="${OPENSHELL_GRPC_ENDPOINT:-http://host.openshell.internal:${PORT}}"
|
|
|
|
DAEMON_ARCH="$(normalize_arch "$(docker info --format '{{.Architecture}}' 2>/dev/null || true)")"
|
|
HOST_OS="$(uname -s)"
|
|
HOST_ARCH="$(normalize_arch "$(uname -m)")"
|
|
SUPERVISOR_TARGET="$(linux_target_triple "${DAEMON_ARCH}")"
|
|
# Cache the supervisor binary alongside the gateway state. Reuses the same
|
|
# Docker pipeline used for the supervisor image. The Dockerfiles do not compile
|
|
# Rust; the cross-compile runs on the host via cargo zigbuild and the binary is
|
|
# staged into the build context. On macOS the host staging path raises the
|
|
# per-process file-descriptor limit automatically (see build-env.sh), so the
|
|
# static musl link no longer hits ProcessFdQuotaExceeded on this many rlibs.
|
|
SUPERVISOR_OUT_DIR="${STATE_DIR}/supervisor/${DAEMON_ARCH}"
|
|
SUPERVISOR_BIN="${SUPERVISOR_OUT_DIR}/openshell-sandbox"
|
|
|
|
install_supervisor_binary() {
|
|
local source=$1
|
|
local staged
|
|
|
|
# A running sandbox may execute a bind-mounted copy of SUPERVISOR_BIN.
|
|
# Replacing its directory entry keeps that old inode alive for the running
|
|
# container without truncating it, while new containers see this build.
|
|
mkdir -p "${SUPERVISOR_OUT_DIR}"
|
|
staged="$(mktemp "${SUPERVISOR_OUT_DIR}/.openshell-sandbox.XXXXXX")"
|
|
if ! cp "${source}" "${staged}" || ! chmod 0755 "${staged}"; then
|
|
rm -f -- "${staged}"
|
|
return 1
|
|
fi
|
|
if ! mv -f -- "${staged}" "${SUPERVISOR_BIN}"; then
|
|
rm -f -- "${staged}"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
CARGO_BUILD_JOBS_ARG=()
|
|
if [[ -n "${CARGO_BUILD_JOBS:-}" ]]; then
|
|
CARGO_BUILD_JOBS_ARG=(-j "${CARGO_BUILD_JOBS}")
|
|
fi
|
|
|
|
echo "Building openshell-gateway..."
|
|
cargo build ${CARGO_BUILD_JOBS_ARG[@]+"${CARGO_BUILD_JOBS_ARG[@]}"} \
|
|
-p openshell-server --bin openshell-gateway
|
|
|
|
TLS_DIR="${STATE_DIR}/tls"
|
|
echo "Generating local gateway credentials..."
|
|
"${GATEWAY_BIN}" generate-certs \
|
|
--output-dir "${TLS_DIR}" \
|
|
--server-san "127.0.0.1" \
|
|
--server-san "localhost" \
|
|
--server-san "host.openshell.internal"
|
|
|
|
echo "Building openshell-sandbox for ${SUPERVISOR_TARGET}..."
|
|
if [[ "${HOST_OS}" == "Linux" && "${HOST_ARCH}" == "${DAEMON_ARCH}" ]]; then
|
|
# Native Linux build — no cross-toolchain required.
|
|
rustup target add "${SUPERVISOR_TARGET}" >/dev/null 2>&1 || true
|
|
cargo build ${CARGO_BUILD_JOBS_ARG[@]+"${CARGO_BUILD_JOBS_ARG[@]}"} \
|
|
-p openshell-sandbox --target "${SUPERVISOR_TARGET}"
|
|
install_supervisor_binary \
|
|
"${ROOT}/target/${SUPERVISOR_TARGET}/debug/openshell-sandbox"
|
|
else
|
|
# Cross-compile through the prebuilt-binary staging helper, then use the
|
|
# supervisor stage to extract just the openshell-sandbox binary.
|
|
#
|
|
# This task is gated on a working Docker daemon above, so pin the
|
|
# container-engine helper to docker — otherwise it auto-detects podman
|
|
# whenever the binary happens to be on PATH.
|
|
mkdir -p "${SUPERVISOR_OUT_DIR}"
|
|
SUPERVISOR_BUILD_DIR="$(mktemp -d "${SUPERVISOR_OUT_DIR}/.build.XXXXXX")"
|
|
if ! CONTAINER_ENGINE=docker \
|
|
DOCKER_PLATFORM="linux/${DAEMON_ARCH}" \
|
|
DOCKER_OUTPUT="type=local,dest=${SUPERVISOR_BUILD_DIR}" \
|
|
bash "${ROOT}/tasks/scripts/docker-build-image.sh" supervisor-output; then
|
|
rm -rf -- "${SUPERVISOR_BUILD_DIR}"
|
|
exit 1
|
|
fi
|
|
if ! install_supervisor_binary "${SUPERVISOR_BUILD_DIR}/openshell-sandbox"; then
|
|
rm -rf -- "${SUPERVISOR_BUILD_DIR}"
|
|
exit 1
|
|
fi
|
|
rm -rf -- "${SUPERVISOR_BUILD_DIR}"
|
|
fi
|
|
|
|
if [[ ! -f "${SUPERVISOR_BIN}" ]]; then
|
|
echo "ERROR: expected supervisor binary at ${SUPERVISOR_BIN}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "${STATE_DIR}"
|
|
CONFIG_PATH="${STATE_DIR}/gateway.toml"
|
|
cat >"${CONFIG_PATH}" <<EOF
|
|
[openshell]
|
|
version = 1
|
|
|
|
[openshell.gateway]
|
|
name = "${GATEWAY_NAME}"
|
|
compute_drivers = ["docker"]
|
|
disable_tls = true
|
|
|
|
[openshell.gateway.otlp]
|
|
endpoint = "http://127.0.0.1:4317"
|
|
|
|
[openshell.gateway.auth]
|
|
allow_unauthenticated_users = true
|
|
|
|
[openshell.gateway.gateway_jwt]
|
|
signing_key_path = "${TLS_DIR}/jwt/signing.pem"
|
|
public_key_path = "${TLS_DIR}/jwt/public.pem"
|
|
kid_path = "${TLS_DIR}/jwt/kid"
|
|
gateway_id = "${GATEWAY_NAME}"
|
|
ttl_secs = 3600
|
|
|
|
[openshell.drivers.docker]
|
|
default_image = "${SANDBOX_IMAGE}"
|
|
image_pull_policy = "${SANDBOX_IMAGE_PULL_POLICY}"
|
|
sandbox_namespace = "${SANDBOX_NAMESPACE}"
|
|
grpc_endpoint = "${GRPC_ENDPOINT}"
|
|
supervisor_bin = "${SUPERVISOR_BIN}"
|
|
EOF
|
|
|
|
GATEWAY_ENDPOINT="http://127.0.0.1:${PORT}"
|
|
register_gateway_metadata "${GATEWAY_NAME}" "${GATEWAY_ENDPOINT}" "${PORT}"
|
|
|
|
echo "Starting standalone Docker gateway..."
|
|
echo " gateway: ${GATEWAY_NAME}"
|
|
echo " endpoint: ${GATEWAY_ENDPOINT}"
|
|
echo " namespace: ${SANDBOX_NAMESPACE}"
|
|
echo " state dir: ${STATE_DIR}"
|
|
echo
|
|
echo "Point the CLI at this gateway with one of:"
|
|
echo " openshell --gateway ${GATEWAY_NAME} status"
|
|
echo " openshell gateway select ${GATEWAY_NAME}"
|
|
echo
|
|
|
|
exec "${GATEWAY_BIN}" \
|
|
--config "${CONFIG_PATH}" \
|
|
--port "${PORT}" \
|
|
--log-level "${LOG_LEVEL}" \
|
|
--drivers docker \
|
|
--disable-tls \
|
|
--db-url "sqlite:${STATE_DIR}/gateway.db?mode=rwc"
|