mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-16 05:20:22 +08:00
* feat(podman): add Podman compute driver for rootless sandbox management Adds openshell-driver-podman, a new compute driver that manages OpenShell sandboxes as rootless Podman containers via the Podman REST API over a Unix socket. Enables local workstation sandboxes without Kubernetes. Driver features: - Bridge networking with ephemeral host-port mapping for rootless SSH reachability - Named volumes for workspace storage, Podman native health checks, GPU via CDI - Supervisor binary sideloaded via image volume mount (BYOC-compatible) - SSH handshake secret injected via Podman secrets API (not plaintext env) - Typed ContainerSpec structs, input validation, and path-traversal guards - Cgroups v2 required; fails fast on v1 hosts - Bounded event stream buffer; watch stream reconnection handled by server watch_loop - Graceful shutdown and standalone driver binary with gRPC bridge Rootless-specific fixes: - Skip drop_privileges when user namespace lacks SETUID/SETGID/DAC_READ_SEARCH caps - Add /run/netns tmpfs mount for ip netns in rootless containers - Use secret_env map (not secrets array) for env-var injection in libpod API - Resolve SSH endpoint to 127.0.0.1:<host_port> instead of unreachable bridge IP Server/sandbox hardening: - Split loopback and link-local SSRF gates; Podman/VM drivers allow loopback - Close SSRF bypass in SSH tunnel Host path by resolving DNS before connecting - Prevent OPENSHELL_* env var override by user-supplied spec environment maps - Disable SQLite pool idle_timeout/max_lifetime for in-memory databases - Emit deleted_event on 404-during-inspect instead of regressing sandbox phase - Key delete cleanup by stable sandbox_id to survive container label drift CLI fixes: - Restore --name as a named flag on sandbox create (not positional) - Fix exec command arg parsing to not consume sandboxed-command flags - Propagate SSH verbosity via OPENSHELL_SSH_LOG_LEVEL Build tooling: - Add tasks/scripts/container-engine.sh: auto-detects Podman or Docker, exposes unified ce_* helpers; all build/cluster/VM scripts updated to use it - Add docker:build:supervisor mise task for standalone supervisor image - Add openshell-driver-podman to Dockerfile.images pre-fetch/build stages - Add e2e/rust/e2e-podman.sh and e2e:podman mise task for full lifecycle testing Signed-off-by: Adam Miller <admiller@redhat.com> * fix(driver-podman): derive grpc endpoint from server bind port When a user starts the gateway on a non-default port (e.g. --port 8081), sandbox containers were receiving OPENSHELL_ENDPOINT pointing at the default port 8080. The driver's auto-detection fallback read OPENSHELL_BIND_ADDRESS from the environment, which was stale or unset, and fell back to DEFAULT_SERVER_PORT. Add gateway_port to PodmanComputeConfig and thread config.bind_address.port() from the server into the driver so the fallback uses the actual listening port. Remove the OPENSHELL_BIND_ADDRESS env var read and the extract_port_from_bind_address helper which are no longer needed. Add --gateway-port / OPENSHELL_GATEWAY_PORT to the standalone driver binary for parity when the driver is run outside the embedded server path. Signed-off-by: Adam Miller <admiller@redhat.com> * fix(driver-podman): address PR feedback on env test safety and cluster DNS docs Replace hand-rolled unsafe TempEnvVar RAII guard with temp_env::with_vars and a static ENV_LOCK mutex, fixing a data race in parallel test execution. The prior safety comment incorrectly claimed Cargo runs tests single-threaded. Update debug-openshell-cluster skill to accurately document the DNS proxy strategy (setup_dns_proxy + public DNS fallback) and clarify the separation between cluster DNS and sandbox agent DNS enforcement. Signed-off-by: Adam Miller <admiller@redhat.com> * fix(e2e): resolve CI failures in auth timeout, test harness, and formatting - Short-circuit browser_auth_flow when OPENSHELL_NO_BROWSER=1 instead of waiting the full 120s AUTH_TIMEOUT for a callback that never arrives - Add timeout to SandboxGuard::create() and create_with_upload() to prevent indefinite hangs (matches create_keep() which already had one) - Add missing '--' separator in no_proxy test before command args - Add #![cfg(feature = "e2e")] gate to sandbox_lifecycle.rs - Run cargo fmt on openshell-driver-podman - Refine cluster DNS docs for Podman in debug-openshell-cluster skill Signed-off-by: Adam Miller <admiller@redhat.com> * refactor(server): remove allows_loopback_endpoints from ComputeRuntime SSRF protection is now handled at the network and proxy layers (openshell-core net.rs, openshell-sandbox proxy.rs) rather than requiring per-driver flags on ComputeRuntime. Update architecture docs to reflect supervisor relay SSH transport and add rootless networking deep-dive. Signed-off-by: Adam Miller <admiller@redhat.com> --------- Signed-off-by: Adam Miller <admiller@redhat.com>
115 lines
4.0 KiB
Bash
Executable File
115 lines
4.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
# Create or reconnect to the persistent "dev" sandbox.
|
|
#
|
|
# - Ensures the cluster is running (bootstraps if needed).
|
|
# - Redeploys if local source has changed since last deploy.
|
|
# - Recreates the sandbox if the cluster was redeployed since the sandbox
|
|
# was last created.
|
|
# - Provisions an "anthropic" provider from $ANTHROPIC_API_KEY when available.
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
source "${SCRIPT_DIR}/container-engine.sh"
|
|
|
|
SANDBOX_NAME="dev"
|
|
CLUSTER_NAME=${CLUSTER_NAME:-$(basename "$PWD")}
|
|
CONTAINER_NAME="openshell-cluster-${CLUSTER_NAME}"
|
|
STATE_DIR=${SANDBOX_STATE_DIR:-.cache}
|
|
SANDBOX_STATE_FILE=${STATE_DIR}/sandbox-dev.state
|
|
DEPLOY_STATE_FILE=${DEPLOY_FAST_STATE_FILE:-${STATE_DIR}/cluster-deploy-fast.state}
|
|
CMD=(${usage_command:-claude})
|
|
|
|
# -------------------------------------------------------------------
|
|
# 1. Ensure the cluster is running; redeploy if dirty
|
|
# -------------------------------------------------------------------
|
|
if ! ce ps -q --filter "name=${CONTAINER_NAME}" | grep -q .; then
|
|
echo "No running cluster found. Bootstrapping..."
|
|
mise run cluster
|
|
else
|
|
# Run incremental deploy — it no-ops when nothing has changed.
|
|
mise run cluster
|
|
fi
|
|
|
|
# Capture the current deploy fingerprint so we can tell later whether the
|
|
# sandbox predates the most recent deploy.
|
|
deploy_fingerprint=""
|
|
if [[ -f "${DEPLOY_STATE_FILE}" ]]; then
|
|
deploy_fingerprint=$(shasum -a 256 "${DEPLOY_STATE_FILE}" | cut -d ' ' -f 1)
|
|
fi
|
|
|
|
# -------------------------------------------------------------------
|
|
# 2. Decide whether to (re)create the sandbox
|
|
# -------------------------------------------------------------------
|
|
need_create=1
|
|
|
|
if openshell sandbox get "${SANDBOX_NAME}" >/dev/null 2>&1; then
|
|
# Sandbox exists — only recreate if the cluster has been redeployed.
|
|
# The command passed via `-- <cmd>` only affects the SSH exec session,
|
|
# not the sandbox pod itself (which always runs `sleep infinity`), so
|
|
# a command change never requires recreation.
|
|
previous_deploy_fingerprint=""
|
|
if [[ -f "${SANDBOX_STATE_FILE}" ]]; then
|
|
while IFS='=' read -r key value; do
|
|
case "${key}" in
|
|
deploy) previous_deploy_fingerprint="${value}" ;;
|
|
esac
|
|
done < "${SANDBOX_STATE_FILE}"
|
|
fi
|
|
|
|
if [[ -n "${deploy_fingerprint}" && "${deploy_fingerprint}" == "${previous_deploy_fingerprint}" ]]; then
|
|
need_create=0
|
|
else
|
|
echo "Cluster has been redeployed since sandbox '${SANDBOX_NAME}' was created. Recreating..."
|
|
openshell sandbox delete "${SANDBOX_NAME}" || true
|
|
fi
|
|
fi
|
|
|
|
# -------------------------------------------------------------------
|
|
# 3. Ensure the anthropic provider exists when the key is available
|
|
# -------------------------------------------------------------------
|
|
ensure_anthropic_provider() {
|
|
if [[ -z "${ANTHROPIC_API_KEY:-}" ]]; then
|
|
return
|
|
fi
|
|
|
|
if openshell provider get anthropic >/dev/null 2>&1; then
|
|
# Provider already registered — nothing to do.
|
|
return
|
|
fi
|
|
|
|
echo "Registering anthropic provider..."
|
|
openshell provider create \
|
|
--name anthropic \
|
|
--type claude \
|
|
--credential "ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}"
|
|
}
|
|
|
|
ensure_anthropic_provider
|
|
|
|
# -------------------------------------------------------------------
|
|
# 4. Create or connect to the sandbox
|
|
# -------------------------------------------------------------------
|
|
PROVIDER_ARGS=()
|
|
if openshell provider get anthropic >/dev/null 2>&1; then
|
|
PROVIDER_ARGS+=(--provider anthropic)
|
|
fi
|
|
|
|
if [[ "${need_create}" == "1" ]]; then
|
|
echo "Creating sandbox '${SANDBOX_NAME}'..."
|
|
openshell sandbox create --name "${SANDBOX_NAME}" "${PROVIDER_ARGS[@]}" --tty -- "${CMD[@]}"
|
|
else
|
|
echo "Connecting to existing sandbox '${SANDBOX_NAME}'..."
|
|
openshell sandbox connect "${SANDBOX_NAME}"
|
|
fi
|
|
|
|
# Record state so we know this sandbox matches the current deploy.
|
|
mkdir -p "$(dirname "${SANDBOX_STATE_FILE}")"
|
|
cat > "${SANDBOX_STATE_FILE}" <<EOF
|
|
deploy=${deploy_fingerprint}
|
|
EOF
|