| #!/usr/bin/env bash |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| set -euo pipefail |
|
|
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| MANIFESTS="$SCRIPT_DIR/manifests" |
| NS="${OPENCLAW_NAMESPACE:-openclaw}" |
|
|
| |
| for cmd in kubectl openssl; do |
| command -v "$cmd" &>/dev/null || { echo "Missing: $cmd" >&2; exit 1; } |
| done |
| kubectl cluster-info &>/dev/null || { echo "Cannot connect to cluster. Check kubeconfig." >&2; exit 1; } |
|
|
| |
| |
| |
| if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then |
| cat <<'HELP' |
| Usage: ./scripts/k8s/deploy.sh [OPTION] |
|
|
| (no args) Deploy OpenClaw (creates secret from env if needed) |
| --create-secret Create or update the K8s Secret from env vars without deploying |
| --show-token Print the gateway token after deploy or secret creation |
| --delete Delete the namespace and all resources |
| -h, --help Show this help |
|
|
| Environment: |
| Export at least one provider API key: |
| ANTHROPIC_API_KEY, GEMINI_API_KEY, OPENAI_API_KEY, OPENROUTER_API_KEY |
|
|
| OPENCLAW_NAMESPACE Kubernetes namespace (default: openclaw) |
| HELP |
| exit 0 |
| fi |
|
|
| SHOW_TOKEN=false |
| MODE="deploy" |
|
|
| while [[ $# -gt 0 ]]; do |
| case "$1" in |
| --create-secret) |
| MODE="create-secret" |
| ;; |
| --delete) |
| MODE="delete" |
| ;; |
| --show-token) |
| SHOW_TOKEN=true |
| ;; |
| *) |
| echo "Unknown option: $1" >&2 |
| echo "Run ./scripts/k8s/deploy.sh --help for usage." >&2 |
| exit 1 |
| ;; |
| esac |
| shift |
| done |
|
|
| |
| |
| |
| if [[ "$MODE" == "delete" ]]; then |
| echo "Deleting namespace '$NS' and all resources..." |
| kubectl delete namespace "$NS" --ignore-not-found |
| echo "Done." |
| exit 0 |
| fi |
|
|
| |
| |
| |
| _apply_secret() { |
| local TMP_DIR |
| local EXISTING_SECRET=false |
| local EXISTING_TOKEN="" |
| local ANTHROPIC_VALUE="" |
| local OPENAI_VALUE="" |
| local GEMINI_VALUE="" |
| local OPENROUTER_VALUE="" |
| local TOKEN |
| local SECRET_MANIFEST |
| TMP_DIR="$(mktemp -d)" |
| chmod 700 "$TMP_DIR" |
| trap 'rm -rf "$TMP_DIR"' EXIT |
|
|
| if kubectl get secret openclaw-secrets -n "$NS" &>/dev/null; then |
| EXISTING_SECRET=true |
| EXISTING_TOKEN="$(kubectl get secret openclaw-secrets -n "$NS" -o jsonpath='{.data.OPENCLAW_GATEWAY_TOKEN}' | base64 -d)" |
| ANTHROPIC_VALUE="$(kubectl get secret openclaw-secrets -n "$NS" -o jsonpath='{.data.ANTHROPIC_API_KEY}' 2>/dev/null | base64 -d)" |
| OPENAI_VALUE="$(kubectl get secret openclaw-secrets -n "$NS" -o jsonpath='{.data.OPENAI_API_KEY}' 2>/dev/null | base64 -d)" |
| GEMINI_VALUE="$(kubectl get secret openclaw-secrets -n "$NS" -o jsonpath='{.data.GEMINI_API_KEY}' 2>/dev/null | base64 -d)" |
| OPENROUTER_VALUE="$(kubectl get secret openclaw-secrets -n "$NS" -o jsonpath='{.data.OPENROUTER_API_KEY}' 2>/dev/null | base64 -d)" |
| fi |
|
|
| TOKEN="${EXISTING_TOKEN:-$(openssl rand -hex 32)}" |
| ANTHROPIC_VALUE="${ANTHROPIC_API_KEY:-$ANTHROPIC_VALUE}" |
| OPENAI_VALUE="${OPENAI_API_KEY:-$OPENAI_VALUE}" |
| GEMINI_VALUE="${GEMINI_API_KEY:-$GEMINI_VALUE}" |
| OPENROUTER_VALUE="${OPENROUTER_API_KEY:-$OPENROUTER_VALUE}" |
| SECRET_MANIFEST="$TMP_DIR/secrets.yaml" |
|
|
| |
| printf '%s' "$TOKEN" > "$TMP_DIR/OPENCLAW_GATEWAY_TOKEN" |
| printf '%s' "$ANTHROPIC_VALUE" > "$TMP_DIR/ANTHROPIC_API_KEY" |
| printf '%s' "$OPENAI_VALUE" > "$TMP_DIR/OPENAI_API_KEY" |
| printf '%s' "$GEMINI_VALUE" > "$TMP_DIR/GEMINI_API_KEY" |
| printf '%s' "$OPENROUTER_VALUE" > "$TMP_DIR/OPENROUTER_API_KEY" |
| chmod 600 \ |
| "$TMP_DIR/OPENCLAW_GATEWAY_TOKEN" \ |
| "$TMP_DIR/ANTHROPIC_API_KEY" \ |
| "$TMP_DIR/OPENAI_API_KEY" \ |
| "$TMP_DIR/GEMINI_API_KEY" \ |
| "$TMP_DIR/OPENROUTER_API_KEY" |
|
|
| kubectl create secret generic openclaw-secrets \ |
| -n "$NS" \ |
| --from-file=OPENCLAW_GATEWAY_TOKEN="$TMP_DIR/OPENCLAW_GATEWAY_TOKEN" \ |
| --from-file=ANTHROPIC_API_KEY="$TMP_DIR/ANTHROPIC_API_KEY" \ |
| --from-file=OPENAI_API_KEY="$TMP_DIR/OPENAI_API_KEY" \ |
| --from-file=GEMINI_API_KEY="$TMP_DIR/GEMINI_API_KEY" \ |
| --from-file=OPENROUTER_API_KEY="$TMP_DIR/OPENROUTER_API_KEY" \ |
| --dry-run=client \ |
| -o yaml > "$SECRET_MANIFEST" |
| chmod 600 "$SECRET_MANIFEST" |
|
|
| kubectl create namespace "$NS" --dry-run=client -o yaml | kubectl apply -f - >/dev/null |
| kubectl apply --server-side --field-manager=openclaw -f "$SECRET_MANIFEST" >/dev/null |
| |
| kubectl annotate secret openclaw-secrets -n "$NS" kubectl.kubernetes.io/last-applied-configuration- >/dev/null 2>&1 || true |
| rm -rf "$TMP_DIR" |
| trap - EXIT |
|
|
| if $EXISTING_SECRET; then |
| echo "Secret updated in namespace '$NS'. Existing gateway token preserved." |
| else |
| echo "Secret created in namespace '$NS'." |
| fi |
|
|
| if $SHOW_TOKEN; then |
| echo "Gateway token: $TOKEN" |
| else |
| echo "Gateway token stored in Secret only." |
| echo "Retrieve it with:" |
| echo " kubectl get secret openclaw-secrets -n $NS -o jsonpath='{.data.OPENCLAW_GATEWAY_TOKEN}' | base64 -d && echo" |
| fi |
| } |
|
|
| |
| |
| |
| if [[ "$MODE" == "create-secret" ]]; then |
| HAS_KEY=false |
| for key in ANTHROPIC_API_KEY OPENAI_API_KEY GEMINI_API_KEY OPENROUTER_API_KEY; do |
| if [[ -n "${!key:-}" ]]; then |
| HAS_KEY=true |
| echo " Found $key in environment" |
| fi |
| done |
|
|
| if ! $HAS_KEY; then |
| echo "No API keys found in environment. Export at least one and re-run:" |
| echo " export <PROVIDER>_API_KEY=\"...\" (ANTHROPIC, GEMINI, OPENAI, or OPENROUTER)" |
| echo " ./scripts/k8s/deploy.sh --create-secret" |
| exit 1 |
| fi |
|
|
| _apply_secret |
| echo "" |
| echo "Now run:" |
| echo " ./scripts/k8s/deploy.sh" |
| exit 0 |
| fi |
|
|
| |
| |
| |
| if ! kubectl get secret openclaw-secrets -n "$NS" &>/dev/null; then |
| HAS_KEY=false |
| for key in ANTHROPIC_API_KEY OPENAI_API_KEY GEMINI_API_KEY OPENROUTER_API_KEY; do |
| [[ -n "${!key:-}" ]] && HAS_KEY=true |
| done |
|
|
| if $HAS_KEY; then |
| echo "Creating secret from environment..." |
| _apply_secret |
| echo "" |
| else |
| echo "No secret found and no API keys in environment." |
| echo "" |
| echo "Export at least one provider API key and re-run:" |
| echo " export <PROVIDER>_API_KEY=\"...\" (ANTHROPIC, GEMINI, OPENAI, or OPENROUTER)" |
| echo " ./scripts/k8s/deploy.sh" |
| exit 1 |
| fi |
| fi |
|
|
| |
| |
| |
| echo "Deploying to namespace '$NS'..." |
| kubectl create namespace "$NS" --dry-run=client -o yaml | kubectl apply -f - >/dev/null |
| kubectl apply -k "$MANIFESTS" -n "$NS" |
| kubectl rollout restart deployment/openclaw -n "$NS" 2>/dev/null || true |
| echo "" |
| echo "Waiting for rollout..." |
| kubectl rollout status deployment/openclaw -n "$NS" --timeout=300s |
| echo "" |
| echo "Done. Access the gateway:" |
| echo " kubectl port-forward svc/openclaw 18789:18789 -n $NS" |
| echo " open http://localhost:18789" |
| echo "" |
| if $SHOW_TOKEN; then |
| echo "Gateway token (paste into Control UI):" |
| echo " $(kubectl get secret openclaw-secrets -n "$NS" -o jsonpath='{.data.OPENCLAW_GATEWAY_TOKEN}' | base64 -d)" |
| echo "" |
| fi |
| echo "Retrieve the gateway token with:" |
| echo " kubectl get secret openclaw-secrets -n $NS -o jsonpath='{.data.OPENCLAW_GATEWAY_TOKEN}' | base64 -d && echo" |
|
|