Files
OpenShell/tasks/scripts/gateway-docker.sh
T
krishicks f68867b869 feat(gateway): identify gateways in exported traces (#2647)
* 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>
2026-08-27 17:27:34 +00:00

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"