Skip to content

Commit 2c1697d

Browse files
committed
refactor(dev): share gateway launcher plumbing
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
1 parent 42c8342 commit 2c1697d

9 files changed

Lines changed: 257 additions & 373 deletions

‎tasks/gateway.toml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ run = "bash tasks/scripts/gateway.sh"
1616
description = "Run a standalone gateway with the bundled Docker compute driver"
1717
run = "bash tasks/scripts/gateway-docker.sh"
1818

19+
["gateway:kubernetes"]
20+
description = "Run a standalone gateway with the bundled Kubernetes compute driver"
21+
run = "bash tasks/scripts/gateway-kubernetes.sh"
22+
1923
["gateway:podman"]
2024
description = "Run a standalone gateway with the bundled Podman compute driver"
2125
run = "bash tasks/scripts/gateway-podman.sh"

‎tasks/scripts/gateway-common.sh‎

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#!/usr/bin/env bash
2+
3+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
# Shared plumbing for the local gateway launchers. Driver-specific configuration
7+
# and runtime setup belong in gateway-<driver>.sh.
8+
9+
command_available() {
10+
command -v "$1" >/dev/null 2>&1
11+
}
12+
13+
require_mise() {
14+
if ! command_available mise; then
15+
echo "ERROR: mise is required to build local gateway artifacts" >&2
16+
exit 1
17+
fi
18+
}
19+
20+
run_mise_task() {
21+
require_mise
22+
mise run "$@"
23+
}
24+
25+
port_is_in_use() {
26+
local port=$1
27+
if command_available lsof; then
28+
lsof -nP -iTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1
29+
return $?
30+
fi
31+
if command_available nc; then
32+
nc -z 127.0.0.1 "${port}" >/dev/null 2>&1
33+
return $?
34+
fi
35+
(echo >/dev/tcp/127.0.0.1/"${port}") >/dev/null 2>&1
36+
}
37+
38+
append_local_otlp_config_if_available() {
39+
local config_path=$1
40+
if ! port_is_in_use 4317; then
41+
echo "OTLP collector not detected on 127.0.0.1:4317; trace export disabled."
42+
return
43+
fi
44+
45+
cat >>"${config_path}" <<'EOF'
46+
47+
[openshell.gateway.otlp]
48+
endpoint = "http://127.0.0.1:4317"
49+
EOF
50+
echo "OTLP trace export enabled for http://127.0.0.1:4317."
51+
}
52+
53+
validate_gateway_name() {
54+
local name=$1
55+
local variable_name=$2
56+
if [[ ! "${name}" =~ ^[A-Za-z0-9._-]+$ ]]; then
57+
echo "ERROR: ${variable_name} must contain only letters, numbers, dots, underscores, or dashes" >&2
58+
exit 2
59+
fi
60+
}
61+
62+
register_local_gateway() {
63+
local name=$1
64+
local endpoint=$2
65+
local port=$3
66+
local select_gateway=${4:-false}
67+
local config_home gateway_dir
68+
69+
config_home="${XDG_CONFIG_HOME:-${HOME}/.config}"
70+
gateway_dir="${config_home}/openshell/gateways/${name}"
71+
72+
mkdir -p "${gateway_dir}"
73+
cat >"${gateway_dir}/metadata.json" <<EOF
74+
{
75+
"name": "${name}",
76+
"gateway_endpoint": "${endpoint}",
77+
"is_remote": false,
78+
"gateway_port": ${port},
79+
"auth_mode": "plaintext"
80+
}
81+
EOF
82+
83+
if [[ "${select_gateway}" == "true" ]]; then
84+
printf '%s' "${name}" >"${config_home}/openshell/active_gateway"
85+
fi
86+
}
87+
88+
ensure_container_runtime_image() {
89+
local engine=$1
90+
local image=$2
91+
local configured_image=$3
92+
local build_target=$4
93+
local role=$5
94+
95+
if [[ -n "${configured_image}" ]]; then
96+
if container_image_exists "${engine}" "${image}"; then
97+
return
98+
fi
99+
echo "ERROR: ${role} image '${image}' not found locally." >&2
100+
echo " Build it with ${engine} or unset its image override to build the local :dev image." >&2
101+
exit 1
102+
fi
103+
104+
echo "Refreshing ${engine} ${role} image (${image})..."
105+
require_mise
106+
CONTAINER_ENGINE="${engine}" IMAGE_TAG=dev mise run "build:docker:${build_target}"
107+
108+
if ! container_image_exists "${engine}" "${image}"; then
109+
echo "ERROR: expected ${role} image '${image}' after build" >&2
110+
exit 1
111+
fi
112+
}
113+
114+
container_image_exists() {
115+
local engine=$1
116+
local image=$2
117+
case "${engine}" in
118+
docker) docker image inspect "${image}" >/dev/null 2>&1 ;;
119+
podman) podman image exists "${image}" >/dev/null 2>&1 ;;
120+
*)
121+
echo "ERROR: unsupported container engine '${engine}'" >&2
122+
return 2
123+
;;
124+
esac
125+
}

‎tasks/scripts/gateway-docker.sh‎

Lines changed: 6 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
set -euo pipefail
2727

2828
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
29+
# shellcheck source=tasks/scripts/gateway-common.sh
30+
source "${ROOT}/tasks/scripts/gateway-common.sh"
2931
# shellcheck source=tasks/scripts/gateway-toml.sh
3032
source "${ROOT}/tasks/scripts/gateway-toml.sh"
3133
# shellcheck source=tasks/scripts/gateway-pull-policy.sh
@@ -41,85 +43,7 @@ SANDBOX_IMAGE_PULL_POLICY="$(normalize_image_pull_policy "${OPENSHELL_SANDBOX_IM
4143
LOG_LEVEL="${OPENSHELL_LOG_LEVEL:-info}"
4244
GATEWAY_BIN="${ROOT}/target/debug/openshell-gateway"
4345

44-
port_is_in_use() {
45-
local port=$1
46-
if command -v lsof >/dev/null 2>&1; then
47-
lsof -nP -iTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1
48-
return $?
49-
fi
50-
if command -v nc >/dev/null 2>&1; then
51-
nc -z 127.0.0.1 "${port}" >/dev/null 2>&1
52-
return $?
53-
fi
54-
(echo >/dev/tcp/127.0.0.1/"${port}") >/dev/null 2>&1
55-
}
56-
57-
ensure_docker_runtime_image() {
58-
local image=$1
59-
local configured_image=$2
60-
local build_target=$3
61-
local role=$4
62-
63-
if [[ -n "${configured_image}" ]]; then
64-
if docker image inspect "${image}" >/dev/null 2>&1; then
65-
return
66-
fi
67-
echo "ERROR: ${role} image '${image}' not found locally." >&2
68-
echo " Build it with Docker or unset its image override to build the local :dev image." >&2
69-
exit 1
70-
fi
71-
72-
# Always run the build pipeline for default development images so source
73-
# changes cannot leave a fixed :dev tag pointing at stale runtime code.
74-
echo "Refreshing Docker ${role} image (${image})..."
75-
CONTAINER_ENGINE=docker IMAGE_TAG=dev mise run "build:docker:${build_target}"
76-
77-
if ! docker image inspect "${image}" >/dev/null 2>&1; then
78-
echo "ERROR: expected ${role} image '${image}' after build" >&2
79-
exit 1
80-
fi
81-
}
82-
83-
append_local_otlp_config_if_available() {
84-
local config_path=$1
85-
if ! port_is_in_use 4317; then
86-
echo "OTLP collector not detected on 127.0.0.1:4317; trace export disabled."
87-
return
88-
fi
89-
90-
cat >>"${config_path}" <<'EOF'
91-
92-
[openshell.gateway.otlp]
93-
endpoint = "http://127.0.0.1:4317"
94-
EOF
95-
echo "OTLP trace export enabled for http://127.0.0.1:4317."
96-
}
97-
98-
register_gateway_metadata() {
99-
local name=$1
100-
local endpoint=$2
101-
local port=$3
102-
local config_home gateway_dir
103-
104-
config_home="${XDG_CONFIG_HOME:-${HOME}/.config}"
105-
gateway_dir="${config_home}/openshell/gateways/${name}"
106-
107-
mkdir -p "${gateway_dir}"
108-
cat >"${gateway_dir}/metadata.json" <<EOF
109-
{
110-
"name": "${name}",
111-
"gateway_endpoint": "${endpoint}",
112-
"is_remote": false,
113-
"gateway_port": ${port},
114-
"auth_mode": "plaintext"
115-
}
116-
EOF
117-
}
118-
119-
if [[ ! "${GATEWAY_NAME}" =~ ^[A-Za-z0-9._-]+$ ]]; then
120-
echo "ERROR: OPENSHELL_DOCKER_GATEWAY_NAME must contain only letters, numbers, dots, underscores, or dashes" >&2
121-
exit 2
122-
fi
46+
validate_gateway_name "${GATEWAY_NAME}" OPENSHELL_DOCKER_GATEWAY_NAME
12347

12448
if ! command -v docker >/dev/null 2>&1; then
12549
echo "ERROR: docker CLI is required" >&2
@@ -135,12 +59,12 @@ if port_is_in_use "${PORT}"; then
13559
exit 2
13660
fi
13761

138-
ensure_docker_runtime_image \
62+
ensure_container_runtime_image docker \
13963
"${SUPERVISOR_IMAGE}" \
14064
"${OPENSHELL_SUPERVISOR_IMAGE:-}" \
14165
supervisor \
14266
supervisor
143-
ensure_docker_runtime_image \
67+
ensure_container_runtime_image docker \
14468
"${SANDBOX_RUNTIME_IMAGE}" \
14569
"${OPENSHELL_SANDBOX_RUNTIME_IMAGE:-}" \
14670
sandbox \
@@ -221,7 +145,7 @@ fi
221145
append_local_otlp_config_if_available "${CONFIG_PATH}"
222146

223147
GATEWAY_ENDPOINT="http://127.0.0.1:${PORT}"
224-
register_gateway_metadata "${GATEWAY_NAME}" "${GATEWAY_ENDPOINT}" "${PORT}"
148+
register_local_gateway "${GATEWAY_NAME}" "${GATEWAY_ENDPOINT}" "${PORT}"
225149

226150
echo "Starting standalone Docker gateway..."
227151
echo " gateway: ${GATEWAY_NAME}"
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#!/usr/bin/env bash
2+
3+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
# Start a standalone openshell-gateway backed by the Kubernetes compute driver.
7+
8+
set -euo pipefail
9+
10+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
11+
# shellcheck source=tasks/scripts/gateway-common.sh
12+
source "${ROOT}/tasks/scripts/gateway-common.sh"
13+
# shellcheck source=tasks/scripts/gateway-pull-policy.sh
14+
source "${ROOT}/tasks/scripts/gateway-pull-policy.sh"
15+
16+
PORT="${OPENSHELL_SERVER_PORT:-8080}"
17+
GATEWAY_NAME="${OPENSHELL_GATEWAY_NAME:-kubernetes-dev}"
18+
STATE_DIR="${OPENSHELL_GATEWAY_STATE_DIR:-${ROOT}/.cache/gateway-kubernetes}"
19+
SANDBOX_NAMESPACE="${OPENSHELL_SANDBOX_NAMESPACE:-kubernetes-dev}"
20+
SANDBOX_IMAGE="${OPENSHELL_SANDBOX_IMAGE:-ghcr.io/nvidia/openshell-community/sandboxes/base:latest}"
21+
SANDBOX_IMAGE_PULL_POLICY="$(normalize_image_pull_policy "${OPENSHELL_SANDBOX_IMAGE_PULL_POLICY:-if_not_present}")"
22+
GRPC_ENDPOINT="${OPENSHELL_GRPC_ENDPOINT:-}"
23+
LOG_LEVEL="${OPENSHELL_LOG_LEVEL:-info}"
24+
PRIMARY_BIND_IP="${OPENSHELL_BIND_ADDRESS:-127.0.0.1}"
25+
GATEWAY_BIN="${OPENSHELL_GATEWAY_BIN:-${ROOT}/target/debug/openshell-gateway}"
26+
27+
validate_gateway_name "${GATEWAY_NAME}" OPENSHELL_GATEWAY_NAME
28+
29+
if port_is_in_use "${PORT}"; then
30+
echo "ERROR: port ${PORT} is already in use; free it or set OPENSHELL_SERVER_PORT" >&2
31+
exit 2
32+
fi
33+
34+
echo "Building openshell-gateway..."
35+
run_mise_task build:gateway
36+
37+
if [[ ! -x "${GATEWAY_BIN}" ]]; then
38+
echo "ERROR: expected gateway binary at ${GATEWAY_BIN}" >&2
39+
exit 1
40+
fi
41+
42+
TLS_DIR="${STATE_DIR}/tls"
43+
echo "Generating local gateway credentials..."
44+
"${GATEWAY_BIN}" generate-certs \
45+
--output-dir "${TLS_DIR}" \
46+
--server-san "127.0.0.1" \
47+
--server-san "localhost" \
48+
--server-san "host.openshell.internal"
49+
50+
mkdir -p "${STATE_DIR}"
51+
CONFIG_PATH="${STATE_DIR}/gateway.toml"
52+
install -m 600 /dev/null "${CONFIG_PATH}"
53+
cat >"${CONFIG_PATH}" <<EOF
54+
[openshell]
55+
version = 2
56+
57+
[openshell.gateway]
58+
name = "${GATEWAY_NAME}"
59+
compute_driver = "kubernetes"
60+
disable_tls = true
61+
62+
[openshell.gateway.otlp]
63+
endpoint = "http://127.0.0.1:4317"
64+
65+
[openshell.gateway.auth]
66+
allow_unauthenticated_users = true
67+
68+
[openshell.gateway.gateway_jwt]
69+
signing_key_path = "${TLS_DIR}/jwt/signing.pem"
70+
public_key_path = "${TLS_DIR}/jwt/public.pem"
71+
kid_path = "${TLS_DIR}/jwt/kid"
72+
gateway_id = "${GATEWAY_NAME}"
73+
ttl_secs = 3600
74+
75+
[openshell.drivers.kubernetes]
76+
namespace = "${SANDBOX_NAMESPACE}"
77+
default_image = "${SANDBOX_IMAGE}"
78+
image_pull_policy = "${SANDBOX_IMAGE_PULL_POLICY}"
79+
EOF
80+
if [[ -n "${GRPC_ENDPOINT}" ]]; then
81+
printf 'grpc_endpoint = "%s"\n' "${GRPC_ENDPOINT}" >>"${CONFIG_PATH}"
82+
fi
83+
84+
GATEWAY_ENDPOINT="http://127.0.0.1:${PORT}"
85+
register_local_gateway "${GATEWAY_NAME}" "${GATEWAY_ENDPOINT}" "${PORT}" true
86+
87+
echo "Starting standalone Kubernetes gateway..."
88+
echo " gateway: ${GATEWAY_NAME}"
89+
echo " endpoint: ${GATEWAY_ENDPOINT}"
90+
echo " bind: ${PRIMARY_BIND_IP}:${PORT}"
91+
echo " namespace: ${SANDBOX_NAMESPACE}"
92+
echo " state dir: ${STATE_DIR}"
93+
echo
94+
echo "Active gateway set to '${GATEWAY_NAME}'. The CLI now targets this gateway by default."
95+
echo
96+
97+
exec "${GATEWAY_BIN}" \
98+
--config "${CONFIG_PATH}" \
99+
--bind-address "${PRIMARY_BIND_IP}" \
100+
--port "${PORT}" \
101+
--log-level "${LOG_LEVEL}" \
102+
--compute-driver kubernetes \
103+
--disable-tls \
104+
--db-url "sqlite:${STATE_DIR}/gateway.db?mode=rwc"

0 commit comments

Comments
 (0)