File size: 8,589 Bytes
fc93158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
#!/usr/bin/env bash
# Deploy OpenClaw to Kubernetes.
#
# Secrets are generated in a temp directory and applied server-side.
# No secret material is ever written to the repo checkout.
#
# Usage:
#   ./scripts/k8s/deploy.sh                   # Deploy (requires API key in env or secret already in cluster)
#   ./scripts/k8s/deploy.sh --create-secret   # Create or update the K8s Secret from env vars
#   ./scripts/k8s/deploy.sh --show-token      # Print the gateway token after deploy
#   ./scripts/k8s/deploy.sh --delete          # Tear down
#
# Environment:
#   OPENCLAW_NAMESPACE   Kubernetes namespace (default: openclaw)
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MANIFESTS="$SCRIPT_DIR/manifests"
NS="${OPENCLAW_NAMESPACE:-openclaw}"

# Check prerequisites
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; }

# ---------------------------------------------------------------------------
# -h / --help
# ---------------------------------------------------------------------------
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

# ---------------------------------------------------------------------------
# --delete
# ---------------------------------------------------------------------------
if [[ "$MODE" == "delete" ]]; then
  echo "Deleting namespace '$NS' and all resources..."
  kubectl delete namespace "$NS" --ignore-not-found
  echo "Done."
  exit 0
fi

# ---------------------------------------------------------------------------
# Create and apply Secret to the cluster
# ---------------------------------------------------------------------------
_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"

  # Write secret material to temp files so kubectl handles encoding safely.
  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
  # Clean up any annotation left by older client-side apply runs.
  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
}

# ---------------------------------------------------------------------------
# --create-secret
# ---------------------------------------------------------------------------
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

# ---------------------------------------------------------------------------
# Check that the secret exists in the cluster
# ---------------------------------------------------------------------------
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

# ---------------------------------------------------------------------------
# Deploy
# ---------------------------------------------------------------------------
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"