|
| 1 | +#!/usr/bin/env bash |
| 2 | +# SPDX-License-Identifier: Apache-2.0 |
| 3 | + |
| 4 | +# start-gateway.sh: run the published OpenShell gateway for a core version |
| 5 | +# with the Docker compute driver, for the E2E lane. |
| 6 | +# |
| 7 | +# Usage: start-gateway.sh <core-version> [--state-dir <dir>] [--default-image <image>] |
| 8 | +# |
| 9 | +# Uses core's own deployment files, deploy/docker/docker-compose.yml and |
| 10 | +# gateway.toml, at the core tag: an init container generates the supervisor |
| 11 | +# JWT keys, and the gateway serves plaintext gRPC on 127.0.0.1:8080 and |
| 12 | +# /readyz on 127.0.0.1:8081. The supervisor and sandbox images are pinned to |
| 13 | +# the same version instead of :latest. --default-image replaces the image that |
| 14 | +# sandboxes run when they don't name one (core's default is plain Ubuntu); the |
| 15 | +# gateway pulls it only when it isn't present locally. Then registers the |
| 16 | +# gateway as "e2e" for the SDK in $XDG_CONFIG_HOME/openshell. Waits up to 15 |
| 17 | +# minutes for the gateway image of a fresh tag to be published. |
| 18 | +# |
| 19 | +# Needs docker with the compose plugin and a host path /var/lib/openshell |
| 20 | +# (core's compose file bind-mounts it at the same path). Meant for throwaway |
| 21 | +# CI hosts: it registers the gateway as the active one, and stop-gateway.sh |
| 22 | +# removes every container labelled with core's default sandbox namespace, |
| 23 | +# including sandboxes of another local gateway that uses the same namespace. |
| 24 | +set -euo pipefail |
| 25 | + |
| 26 | +core_repo=${CORE_REPO:-NVIDIA/OpenShell} |
| 27 | +registry=ghcr.io/nvidia/openshell |
| 28 | +name=e2e |
| 29 | +port=8080 |
| 30 | +health_port=8081 |
| 31 | + |
| 32 | +tag= |
| 33 | +default_image= |
| 34 | +state=${OPENSHELL_E2E_STATE:-${RUNNER_TEMP:-/tmp}/openshell-e2e} |
| 35 | +while [ $# -gt 0 ]; do |
| 36 | + case $1 in |
| 37 | + --state-dir) state=${2:?--state-dir needs a path}; shift 2 ;; |
| 38 | + --default-image) default_image=${2:?--default-image needs an image}; shift 2 ;; |
| 39 | + -*) echo "usage: $0 <core-version> [--state-dir <dir>] [--default-image <image>]" >&2; exit 2 ;; |
| 40 | + *) tag=$1; shift ;; |
| 41 | + esac |
| 42 | +done |
| 43 | +[[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-pre\.[0-9]+)?$ ]] || { |
| 44 | + echo "usage: $0 <core-version> (vX.Y.Z or vX.Y.Z-pre.N)" >&2 |
| 45 | + exit 2 |
| 46 | +} |
| 47 | +semver=${tag#v} |
| 48 | +mkdir -p "$state" |
| 49 | +echo "$tag" >"$state/core-version" |
| 50 | + |
| 51 | +# Core pushes images after tagging; a proposal can be faster than that. |
| 52 | +manifest_exists() { |
| 53 | + local token |
| 54 | + token=$(curl -fsS "https://ghcr.io/token?scope=repository:nvidia/openshell/gateway:pull" | jq -r .token) || return 1 |
| 55 | + curl -fsS -o /dev/null -H "Authorization: Bearer $token" \ |
| 56 | + -H 'Accept: application/vnd.oci.image.index.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.oci.image.manifest.v1+json' \ |
| 57 | + "https://ghcr.io/v2/nvidia/openshell/gateway/manifests/$semver" |
| 58 | +} |
| 59 | +for i in $(seq 1 15); do |
| 60 | + manifest_exists && break |
| 61 | + if [ "$i" -eq 15 ]; then |
| 62 | + echo "ERROR: gateway image for $semver not published yet, re-run later" >&2 |
| 63 | + exit 1 |
| 64 | + fi |
| 65 | + echo "Waiting for $registry/gateway:$semver ($i/15) ..." |
| 66 | + sleep 60 |
| 67 | +done |
| 68 | + |
| 69 | +# Core's deployment files at the tag. |
| 70 | +base="https://raw.githubusercontent.com/$core_repo/$tag/deploy/docker" |
| 71 | +curl -fsSL "$base/docker-compose.yml" -o "$state/docker-compose.yml" |
| 72 | +curl -fsSL "$base/gateway.toml" -o "$state/gateway.toml" |
| 73 | +sed -i.bak \ |
| 74 | + -e "s#\"$registry/supervisor:latest\"#\"$registry/supervisor:$semver\"#" \ |
| 75 | + -e "s#\"$registry/sandbox:latest\"#\"$registry/sandbox:$semver\"#" \ |
| 76 | + "$state/gateway.toml" && rm -f "$state/gateway.toml.bak" |
| 77 | +for img in supervisor sandbox; do |
| 78 | + grep -q "\"$registry/$img:$semver\"" "$state/gateway.toml" || { |
| 79 | + echo "ERROR: could not pin the $img image in core's gateway.toml at $tag" >&2 |
| 80 | + exit 1 |
| 81 | + } |
| 82 | +done |
| 83 | +# Core's file binds the health server to loopback inside the container, where |
| 84 | +# the published port can't reach it. Bind it to all container interfaces; the |
| 85 | +# compose file publishes the port on the host's 127.0.0.1 only. |
| 86 | +sed -i.bak 's#^health_bind_address *= *"127\.0\.0\.1:8081"#health_bind_address = "0.0.0.0:8081"#' \ |
| 87 | + "$state/gateway.toml" && rm -f "$state/gateway.toml.bak" |
| 88 | +grep -q '^health_bind_address = "0.0.0.0:8081"' "$state/gateway.toml" || { |
| 89 | + echo "ERROR: could not rebind the health server in core's gateway.toml at $tag" >&2 |
| 90 | + exit 1 |
| 91 | +} |
| 92 | + |
| 93 | +if [ -n "$default_image" ]; then |
| 94 | + sed -i.bak "s#^default_image *= *\".*\"#default_image = \"$default_image\"#" \ |
| 95 | + "$state/gateway.toml" && rm -f "$state/gateway.toml.bak" |
| 96 | + grep -qF "default_image = \"$default_image\"" "$state/gateway.toml" || { |
| 97 | + echo "ERROR: could not set default_image in core's gateway.toml at $tag" >&2 |
| 98 | + exit 1 |
| 99 | + } |
| 100 | +fi |
| 101 | + |
| 102 | +compose() { IMAGE_TAG=$semver docker compose -p openshell-e2e -f "$state/docker-compose.yml" "$@"; } |
| 103 | +echo "Starting gateway $semver ..." |
| 104 | +compose up -d --quiet-pull |
| 105 | + |
| 106 | +for i in $(seq 1 90); do |
| 107 | + if curl -fsS -o /dev/null "http://127.0.0.1:$health_port/readyz"; then |
| 108 | + break |
| 109 | + fi |
| 110 | + if [ "$i" -eq 90 ]; then |
| 111 | + echo "ERROR: gateway not ready after 180 s" >&2 |
| 112 | + compose logs --no-color gateway | tail -n 80 >&2 || true |
| 113 | + exit 1 |
| 114 | + fi |
| 115 | + sleep 2 |
| 116 | +done |
| 117 | +echo "Gateway $semver ready on 127.0.0.1:$port." |
| 118 | + |
| 119 | +config_home=${XDG_CONFIG_HOME:-$HOME/.config} |
| 120 | +dir="$config_home/openshell/gateways/$name" |
| 121 | +mkdir -p "$dir" |
| 122 | +cat >"$dir/metadata.json" <<JSON |
| 123 | +{ |
| 124 | + "name": "$name", |
| 125 | + "gateway_endpoint": "http://127.0.0.1:$port", |
| 126 | + "is_remote": false, |
| 127 | + "gateway_port": $port, |
| 128 | + "auth_mode": "plaintext" |
| 129 | +} |
| 130 | +JSON |
| 131 | +printf '%s' "$name" >"$config_home/openshell/active_gateway" |
| 132 | +echo "Registered gateway '$name' in $config_home/openshell; run the suite with OPENSHELL_GATEWAY=$name." |
0 commit comments