Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ make push-sandbox push-launcher push-gateway push-supervisor
./setup-providers.sh

# 4. Launch a sandbox
kubectl apply -f sandbox.yaml
# or: ./sandbox-ocp.sh
```

Expand All @@ -69,7 +68,6 @@ kubectl apply -f sandbox.yaml
| `setup-providers.sh` | Register credential providers — works on any gateway |
| `sandbox-podman.sh` | Launch sandbox on local gateway (direct CLI) |
| `sandbox-ocp.sh` | Launch sandbox on OpenShift (kubectl apply) |
| `teardown-ocp.sh` | Remove all OpenShift resources |
| `sandbox/Dockerfile` | Custom sandbox image (extends community base) |
| `sandbox/policy.yaml` | Network policy (endpoints not covered by provider profiles) |
| `sandbox/startup.sh` | Runtime env, GWS, MCP config |
Expand Down Expand Up @@ -98,7 +96,6 @@ See [credentials.md](credentials.md) for the full reference.
./sandbox-podman.sh --name dev # named sandbox

# OpenShift
./sandbox-ocp.sh # apply sandbox.yaml + show logs
./sandbox-ocp.sh my-config.yaml # use a custom config

# Either platform
Expand Down
2 changes: 1 addition & 1 deletion credentials.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ How credentials flow from your machine into sandboxes.
| GitHub token | `github` (built-in) | Bearer auth, proxy-resolved | `setup-providers.sh` |
| Vertex AI | `google-vertex-ai` (built-in) | `inference.local` gateway routing, OAuth refresh | `setup-providers.sh` |
| Atlassian API token | `atlassian` (custom profile) | Basic auth, proxy decodes base64 + resolves | `setup-providers.sh` |
| Atlassian URL/username | — | Uploaded as `atlassian.json` (not secrets) | `sandbox-podman.sh` / `sandbox.yaml` |
| Atlassian URL/username | — | Uploaded as `atlassian.json` (not secrets) | `sandbox-podman.sh` / `sandbox-ocp.sh` |
| GWS OAuth | — | Decrypted export uploaded as files | `sandbox-podman.sh` / `setup-creds.sh` |

## How it works
Expand Down
20 changes: 0 additions & 20 deletions delete-sandboxes.sh

This file was deleted.

72 changes: 72 additions & 0 deletions lib/agent.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
# Agent config parsing helpers.
#
# Source from any script:
# source "$(dirname "$0")/lib/agent.sh"
#
# Usage:
# parse_agent agents/default.toml
# # Sets: SANDBOX_IMAGE, SANDBOX_COMMAND, SANDBOX_NAME,
# # SANDBOX_PROVIDERS, SANDBOX_ENV, SANDBOX_KEEP

parse_agent() {
local agent_file="$1"
[[ -f "$agent_file" ]] || { echo "ERROR: $agent_file not found."; exit 1; }

eval "$(python3 -c "
import tomllib, sys, shlex
with open(sys.argv[1], 'rb') as f:
c = tomllib.load(f)
print(f'SANDBOX_NAME={shlex.quote(c.get(\"name\", \"agent\"))}')
print(f'SANDBOX_IMAGE={shlex.quote(c.get(\"image\", \"\"))}')
print(f'SANDBOX_COMMAND={shlex.quote(c.get(\"command\", \"claude --bare\"))}')
print(f'SANDBOX_KEEP={shlex.quote(str(c.get(\"keep\", True)).lower())}')
providers = c.get('providers', [])
print(f'SANDBOX_PROVIDERS={shlex.quote(\" \".join(providers))}')
env = c.get('env', {})
lines = [f'export {k}={v}' for k, v in env.items()]
print(f'SANDBOX_ENV={shlex.quote(chr(10).join(lines) + chr(10))}')
" "$agent_file")"
}

# Build provider flags array from SANDBOX_PROVIDERS.
# Sets: PROVIDER_FLAGS array
build_provider_flags() {
PROVIDER_FLAGS=()
for name in $SANDBOX_PROVIDERS; do
if "$CLI" provider get "$name" &>/dev/null; then
PROVIDER_FLAGS+=(--provider "$name")
echo " $name: attached"
else
echo " $name: not registered (skipping)"
fi
done
}

# Stage sandbox.env + GWS credentials to a directory for upload.
# The directory name must be "openshell" so upload lands at /sandbox/.config/openshell/.
#
# Usage:
# stage_harness_dir /tmp/openshell
# # Creates: $dir/sandbox.env, $dir/credentials.json, $dir/client_secret.json
stage_harness_dir() {
local dir="$1"
mkdir -p "$dir"

if [[ -n "${SANDBOX_ENV:-}" ]]; then
echo "$SANDBOX_ENV" > "$dir/sandbox.env"
fi

if command -v gws &>/dev/null && gws auth status &>/dev/null 2>&1; then
gws auth export --unmasked > "$dir/credentials.json" 2>/dev/null
cp ~/.config/gws/client_secret.json "$dir/client_secret.json" 2>/dev/null || true
echo " GWS: exported"
else
echo " GWS: not configured (skipping)"
fi
}

# Strip ANSI escape codes from stdin.
strip_ansi() {
sed 's/\x1b\[[0-9;]*m//g'
}
126 changes: 6 additions & 120 deletions lib/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,102 +6,39 @@

# ── CLI detection ──────────────────────────────────────────────────────

# The openshell binary. Override with OPENSHELL_CLI for dev builds.
CLI="${OPENSHELL_CLI:-openshell}"

# Guard: exit if openshell CLI is not on PATH.
# Used by: sandbox-podman.sh, sandbox-ocp.sh, openshell-harness-preflight.sh, deploy-podman.sh
require_cli() {
command -v "$CLI" &>/dev/null || { echo "ERROR: openshell CLI not found."; exit 1; }
}

# Guard: exit if kubectl is not on PATH.
# Used by: sandbox-ocp.sh, deploy-ocp.sh, setup-creds.sh, teardown-ocp.sh
require_kubectl() {
command -v kubectl &>/dev/null || { echo "ERROR: kubectl is required."; exit 1; }
}

# ── Gateway validation ────────────────────────────────────────────────

# Get the active gateway name from the CLI.
active_gateway() {
"$CLI" gateway list 2>/dev/null | awk '/^\*/ {print $2}'
}

# Guard: exit if active gateway is remote (wrong platform).
# Used by: sandbox-podman.sh, deploy-podman.sh
require_local_gateway() {
local gw
gw=$(active_gateway)
if [[ "$gw" == *"-remote-"* ]]; then
echo "ERROR: Active gateway '$gw' is remote."
echo " Switch to local: openshell gateway select openshell-local-podman"
exit 1
fi
}

# Guard: exit if active gateway is not remote.
# Used by: sandbox-ocp.sh
require_ocp_gateway() {
local gw
gw=$(active_gateway)
if [[ "$gw" != *"-remote-"* ]]; then
echo "ERROR: Active gateway '$gw' is not remote."
echo " Switch to remote: openshell gateway select openshell-remote-ocp"
gw=$("$CLI" gateway list 2>/dev/null | awk '/127\.0\.0\.1/ {print $1; exit}')
if [[ -z "$gw" ]]; then
echo "ERROR: No local gateway registered."
echo " Install OpenShell or run: ./deploy-podman.sh"
exit 1
fi
}

# ── Provider detection ─────────────────────────────────────────────────

# Space-separated provider names to attach. Override with OPENSHELL_PROVIDERS.
PROVIDER_NAMES="${OPENSHELL_PROVIDERS:-github vertex-local atlassian}"

# Query the gateway for registered providers, build --provider flags.
# Sets PROVIDER_FLAGS array. Skips unregistered providers with a warning.
# Used by: sandbox-podman.sh, openshell-harness-preflight.sh
detect_providers() {
PROVIDER_FLAGS=()
echo "=== Providers ==="
for name in $PROVIDER_NAMES; do
if "$CLI" provider get "$name" &>/dev/null; then
PROVIDER_FLAGS+=(--provider "$name")
echo " $name: attached"
else
echo " $name: not registered (skipping)"
fi
done
}

# ── Environment pre-flight ─────────────────────────────────────────────

# Check which features are enabled based on harness.toml.
# Reads provider definitions and validates env vars, paths, and commands.
# Does not exit — informational only. Use --strict to fail on missing required.
# Used by: openshell-harness-preflight.sh, or standalone: source lib/common.sh && check_env
check_env() {
local script_dir
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
python3 "$script_dir/lib/providers.py" check "$@"
}

# Print space-separated names of providers whose inputs are all available.
# Used by: detect_providers (replaces hardcoded PROVIDER_NAMES when TOML exists)
available_providers() {
local script_dir
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
python3 "$script_dir/lib/providers.py" available
}

# ── Credential staging ─────────────────────────────────────────────────
# These functions stage credentials into a temp dir for --upload into
# sandboxes. Needed because OpenShell doesn't support file-based
# credential projection yet (#1268, #1423).
# ── GWS credential export ─────────────────────────────────────────────
# Used by: setup-creds.sh (K8s secret creation)

# Export decrypted GWS OAuth credentials into $outdir/gws-config/.
# The gws CLI encrypts credentials with a machine-specific key, so we
# must decrypt and re-export for use inside the sandbox container.
# Used by: sandbox-podman.sh (via stage_sandbox_creds), setup-creds.sh
export_gws_creds() {
local outdir="$1"
if ! command -v gws &>/dev/null; then
Expand All @@ -125,54 +62,3 @@ export_gws_creds() {
return 1
fi
}

# Write a sourceable sandbox.env with non-secret env vars to inject into
# the sandbox. These are values that aren't provider-managed (not secrets)
# but need to be available inside the sandbox. The file is sourced by
# startup.sh. Add new env vars here as integrations grow.
# Used by: sandbox-podman.sh (via stage_sandbox_creds), setup-creds.sh
write_sandbox_env() {
local outfile="$1"
mkdir -p "$(dirname "$outfile")"
local has_vars=false

{
# Atlassian non-secret config
if [[ -n "${JIRA_URL:-}" ]]; then
echo "export JIRA_URL=\"$JIRA_URL\""
echo "export JIRA_USERNAME=\"${JIRA_USERNAME:-}\""
echo "export CONFLUENCE_URL=\"${JIRA_URL%/}/wiki\""
echo "export CONFLUENCE_USERNAME=\"${JIRA_USERNAME:-}\""
echo "export CONFLUENCE_API_TOKEN=\"\${JIRA_API_TOKEN:-}\""
has_vars=true
echo " Atlassian: $JIRA_URL" >&2
fi

# Add more env var blocks here as needed:
# if [[ -n "${SOME_VAR:-}" ]]; then
# echo "export SOME_VAR=\"$SOME_VAR\""
# has_vars=true
# fi
} > "$outfile"

$has_vars || { rm -f "$outfile"; echo " sandbox.env: no vars to inject (skipping)" >&2; return 1; }
return 0
}

# Stage all sandbox credentials into a temp dir for --upload.
# Sets UPLOAD_ARGS (array) and STAGE_DIR (path). The caller should not
# clean up STAGE_DIR if using exec (the upload happens during exec).
# Used by: sandbox-podman.sh
stage_sandbox_creds() {
STAGE_DIR=$(mktemp -d)
local creds="$STAGE_DIR/creds"
mkdir -p "$creds"
local has_uploads=false

echo "=== Credentials ==="
export_gws_creds "$creds" && has_uploads=true
write_sandbox_env "$creds/sandbox.env" && has_uploads=true

UPLOAD_ARGS=()
$has_uploads && UPLOAD_ARGS=(--upload "$creds:/sandbox/.harness")
}
2 changes: 1 addition & 1 deletion openshell.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#
# Copy this file and customize per deployment:
# cp openshell.toml my-team.toml
# ./sandbox-check.sh --config my-team.toml
# ./openshell-harness-preflight.sh

# Which providers to register and attach to sandboxes.
# Must match names defined in providers.toml.
Expand Down
Loading