Files
OpenShell/tasks/scripts/gateway.sh
T
Naveen Malik f1ed347a40 fix(driver-podman): bind gateway to 0.0.0.0 in rootless mode (#1623)
Rootless Podman sandbox containers reach the host through pasta's local
connection bypass, which translates L2 frames to L4 host sockets. The
dev gateway script binds to 127.0.0.1 by default, which is not routable
through pasta. Auto-detect rootless mode and bind to 0.0.0.0 so sandbox
containers can connect to the gateway.

- Auto-detect rootless Podman in gateway.sh and export
  OPENSHELL_BIND_ADDRESS=0.0.0.0 when not explicitly set
- Add e2e:podman:rootless mise task and CI matrix entry to validate
  rootless Podman networking end-to-end
- CI creates a non-root user inside the privileged container to trigger
  Podman's rootless code paths (pasta, user namespace isolation)

Signed-off-by: Naveen Malik <nmalik@redhat.com>
2026-05-29 13:03:26 -05:00

381 lines
10 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 using the detected compute driver.
#
# Auto-detection follows the gateway's runtime order:
# Kubernetes -> Podman -> Docker
#
# VM/MicroVM is intentionally explicit-only because it requires runtime setup.
# Use either:
# OPENSHELL_DRIVERS=vm mise run gateway
# mise run gateway:vm
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
GATEWAY_BIN="${ROOT}/target/debug/openshell-gateway"
usage() {
cat <<'EOF'
Usage: mise run gateway [-- --driver DRIVER]
Start a local OpenShell gateway with the detected compute driver.
Driver detection order:
kubernetes -> podman -> docker
Options:
--driver DRIVER Override detection. Accepted values:
kubernetes, podman, docker, vm, microvm
-h, --help Show this help.
Environment:
OPENSHELL_DRIVERS Driver override used by openshell-gateway.
OPENSHELL_GATEWAY_NAME Gateway name for generic podman/kubernetes runs.
OPENSHELL_SERVER_PORT Gateway port. Defaults to 8080 for Kubernetes,
18080 for Podman/Docker, and 18081 for VM.
OPENSHELL_SUPERVISOR_IMAGE
Podman supervisor sideload image. Defaults to
openshell/supervisor:dev and is built on demand.
Docker and VM runs delegate to gateway:docker and gateway:vm setup scripts.
EOF
}
normalize_driver() {
local driver
driver="$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')"
case "${driver}" in
kubernetes|k8s) echo "kubernetes" ;;
podman) echo "podman" ;;
docker) echo "docker" ;;
vm|microvm) echo "vm" ;;
"")
echo "ERROR: empty driver value" >&2
exit 2
;;
*)
echo "ERROR: unsupported driver '$1' (expected kubernetes, podman, docker, vm, or microvm)" >&2
exit 2
;;
esac
}
command_available() {
command -v "$1" >/dev/null 2>&1
}
require_mise() {
if ! command_available mise; then
echo "ERROR: mise is required to build local gateway artifacts" >&2
exit 1
fi
}
run_mise_task() {
require_mise
mise run "$@"
}
podman_available() {
command_available podman && podman info >/dev/null 2>&1
}
docker_available() {
command_available docker && docker info >/dev/null 2>&1
}
detect_driver() {
if [[ -n "${KUBERNETES_SERVICE_HOST:-}" ]]; then
echo "kubernetes"
return
fi
if podman_available; then
echo "podman"
return
fi
if docker_available; then
echo "docker"
return
fi
echo "ERROR: no compute driver detected." >&2
echo " Start Podman or Docker, run inside Kubernetes, or set OPENSHELL_DRIVERS." >&2
exit 2
}
port_is_in_use() {
local port=$1
if command_available lsof; then
lsof -nP -iTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1
return $?
fi
if command_available nc; 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
printf '%s' "${name}" >"${config_home}/openshell/active_gateway"
}
require_podman_service() {
if ! command_available podman; then
echo "ERROR: podman is not installed or not in PATH" >&2
exit 1
fi
if ! podman_available; then
echo "ERROR: podman service is not reachable. Start it with:" >&2
if [[ "$(uname -s)" == "Darwin" ]]; then
echo " podman machine start" >&2
else
echo " systemctl --user start podman.socket" >&2
fi
exit 1
fi
}
ensure_podman_supervisor_image() {
local supervisor_image=$1
if podman image exists "${supervisor_image}" >/dev/null 2>&1; then
return
fi
if [[ -n "${OPENSHELL_SUPERVISOR_IMAGE:-}" ]]; then
echo "ERROR: supervisor image '${supervisor_image}' not found locally." >&2
echo " Build it with Podman or unset OPENSHELL_SUPERVISOR_IMAGE to build openshell/supervisor:dev." >&2
exit 1
fi
echo "Building Podman supervisor sideload image (${supervisor_image})..."
require_mise
CONTAINER_ENGINE=podman IMAGE_TAG=dev mise run build:docker:supervisor
if ! podman image exists "${supervisor_image}" >/dev/null 2>&1; then
echo "ERROR: expected supervisor image '${supervisor_image}' after build" >&2
exit 1
fi
}
podman_pull_policy() {
case "$1" in
Always|always) echo "always" ;;
IfNotPresent|ifnotpresent|missing|"") echo "missing" ;;
Never|never) echo "never" ;;
Newer|newer) echo "newer" ;;
*)
echo "ERROR: unsupported Podman image pull policy '$1'" >&2
exit 2
;;
esac
}
explicit_driver=""
while [[ "$#" -gt 0 ]]; do
case "$1" in
--driver)
if [[ "$#" -lt 2 ]]; then
echo "ERROR: --driver requires a value" >&2
exit 2
fi
explicit_driver="$(normalize_driver "$2")"
shift 2
;;
--driver=*)
explicit_driver="$(normalize_driver "${1#--driver=}")"
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "ERROR: unknown gateway option '$1'" >&2
echo >&2
usage >&2
exit 2
;;
esac
done
if [[ -n "${explicit_driver}" && -n "${OPENSHELL_DRIVERS:-}" ]]; then
echo "ERROR: use either --driver or OPENSHELL_DRIVERS, not both" >&2
exit 2
fi
if [[ -z "${explicit_driver}" && -n "${OPENSHELL_DRIVERS:-}" ]]; then
if [[ "${OPENSHELL_DRIVERS}" == *,* ]]; then
echo "ERROR: mise run gateway supports one driver; got OPENSHELL_DRIVERS=${OPENSHELL_DRIVERS}" >&2
exit 2
fi
explicit_driver="$(normalize_driver "${OPENSHELL_DRIVERS}")"
fi
DRIVER="${explicit_driver:-$(detect_driver)}"
case "${DRIVER}" in
docker)
export OPENSHELL_DOCKER_GATEWAY_NAME="${OPENSHELL_DOCKER_GATEWAY_NAME:-${OPENSHELL_GATEWAY_NAME:-docker-dev}}"
exec bash "${ROOT}/tasks/scripts/gateway-docker.sh"
;;
vm)
export OPENSHELL_VM_GATEWAY_NAME="${OPENSHELL_VM_GATEWAY_NAME:-${OPENSHELL_GATEWAY_NAME:-vm-dev}}"
exec bash "${ROOT}/tasks/scripts/gateway-vm.sh"
;;
esac
DEFAULT_PORT="18080"
if [[ "${DRIVER}" == "kubernetes" ]]; then
DEFAULT_PORT="8080"
fi
PORT="${OPENSHELL_SERVER_PORT:-${DEFAULT_PORT}}"
GATEWAY_NAME="${OPENSHELL_GATEWAY_NAME:-${DRIVER}-dev}"
STATE_DIR="${OPENSHELL_GATEWAY_STATE_DIR:-${ROOT}/.cache/gateway-${DRIVER}}"
SANDBOX_NAMESPACE="${OPENSHELL_SANDBOX_NAMESPACE:-${DRIVER}-dev}"
SANDBOX_IMAGE="${OPENSHELL_SANDBOX_IMAGE:-ghcr.io/nvidia/openshell-community/sandboxes/base:latest}"
SANDBOX_IMAGE_PULL_POLICY="${OPENSHELL_SANDBOX_IMAGE_PULL_POLICY:-IfNotPresent}"
GRPC_ENDPOINT="${OPENSHELL_GRPC_ENDPOINT:-}"
LOG_LEVEL="${OPENSHELL_LOG_LEVEL:-info}"
if [[ "${DRIVER}" == "podman" ]]; then
require_podman_service
SUPERVISOR_IMAGE="${OPENSHELL_SUPERVISOR_IMAGE:-openshell/supervisor:dev}"
ensure_podman_supervisor_image "${SUPERVISOR_IMAGE}"
export OPENSHELL_SUPERVISOR_IMAGE="${SUPERVISOR_IMAGE}"
# Rootless Podman containers reach the host via pasta's local connection
# bypass, which translates to host L4 sockets. The gateway must listen on
# 0.0.0.0 so pasta can reach it — 127.0.0.1 is not routable through pasta.
if [[ -z "${OPENSHELL_BIND_ADDRESS:-}" ]]; then
if podman info --format '{{.Host.Security.Rootless}}' 2>/dev/null | grep -q true; then
export OPENSHELL_BIND_ADDRESS="0.0.0.0"
fi
fi
fi
if [[ ! "${GATEWAY_NAME}" =~ ^[A-Za-z0-9._-]+$ ]]; then
echo "ERROR: OPENSHELL_GATEWAY_NAME must contain only letters, numbers, dots, underscores, or dashes" >&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
echo "Building openshell-gateway..."
run_mise_task build:gateway
if [[ ! -x "${GATEWAY_BIN}" ]]; then
echo "ERROR: expected gateway binary at ${GATEWAY_BIN}" >&2
exit 1
fi
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"
mkdir -p "${STATE_DIR}"
CONFIG_PATH="${STATE_DIR}/gateway.toml"
cat >"${CONFIG_PATH}" <<EOF
[openshell]
version = 1
[openshell.gateway]
compute_drivers = ["${DRIVER}"]
default_image = "${SANDBOX_IMAGE}"
disable_tls = true
[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
EOF
case "${DRIVER}" in
kubernetes)
cat >>"${CONFIG_PATH}" <<EOF
sandbox_namespace = "${SANDBOX_NAMESPACE}"
[openshell.drivers.kubernetes]
namespace = "${SANDBOX_NAMESPACE}"
image_pull_policy = "${SANDBOX_IMAGE_PULL_POLICY}"
EOF
if [[ -n "${GRPC_ENDPOINT}" ]]; then
printf 'grpc_endpoint = "%s"\n' "${GRPC_ENDPOINT}" >>"${CONFIG_PATH}"
fi
;;
podman)
cat >>"${CONFIG_PATH}" <<EOF
supervisor_image = "${OPENSHELL_SUPERVISOR_IMAGE}"
[openshell.drivers.podman]
image_pull_policy = "$(podman_pull_policy "${SANDBOX_IMAGE_PULL_POLICY}")"
EOF
if [[ -n "${GRPC_ENDPOINT}" ]]; then
printf 'grpc_endpoint = "%s"\n' "${GRPC_ENDPOINT}" >>"${CONFIG_PATH}"
fi
;;
esac
GATEWAY_ENDPOINT="http://127.0.0.1:${PORT}"
register_gateway_metadata "${GATEWAY_NAME}" "${GATEWAY_ENDPOINT}" "${PORT}"
echo "Starting standalone ${DRIVER} gateway..."
echo " gateway: ${GATEWAY_NAME}"
echo " endpoint: ${GATEWAY_ENDPOINT}"
echo " namespace: ${SANDBOX_NAMESPACE}"
echo " state dir: ${STATE_DIR}"
if [[ "${DRIVER}" == "podman" ]]; then
echo " supervisor image: ${OPENSHELL_SUPERVISOR_IMAGE}"
fi
echo
echo "Active gateway set to '${GATEWAY_NAME}'. The CLI now targets this gateway by default."
echo
exec "${GATEWAY_BIN}" \
--config "${CONFIG_PATH}" \
--port "${PORT}" \
--log-level "${LOG_LEVEL}" \
--drivers "${DRIVER}" \
--disable-tls \
--db-url "sqlite:${STATE_DIR}/gateway.db?mode=rwc"