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
6 changes: 6 additions & 0 deletions charts/openab-feishu/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: v2
name: openab-feishu
description: OpenAB + Feishu/Lark — single-pod deployment with gateway (WebSocket by default, optional Cloudflare Tunnel for webhook mode).
type: application
version: 0.1.0
appVersion: "0.8.3"
220 changes: 220 additions & 0 deletions charts/openab-feishu/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
# openab-feishu

OpenAB + Feishu/Lark — single-pod Helm chart for deploying an AI agent on Feishu (飛書) or Lark.

## Architecture

```
┌──────────── Pod: openab-feishu ────────────┐
│ │
│ ┌──────────┐ localhost ┌─────────────┐ │
│ │ openab │ ◄──────────► │ gateway │ │
│ │ (agent) │ │ (feishu) │ │
│ └──────────┘ └──────┬──────┘ │
│ │ │ │
│ ▼ ▼ │
│ PVC /home/agent Outbound WebSocket │
│ to open.feishu.cn │
└─────────────────────────────────────────────┘
```

**Default mode: WebSocket** — gateway connects outbound to Feishu, no public endpoint needed.

Optional webhook mode adds a cloudflared sidecar (same pattern as `openab-telegram`).

## Quick Start

```bash
helm install my-bot ./charts/openab-feishu \
--set feishu.appId="cli_xxx" \
--set feishu.appSecret="xxx" \
--namespace openab --create-namespace
```

Only **2 required parameters**. Everything else has sane defaults.

## Prerequisites: Feishu Open Platform Setup

Before deploying, create a Feishu app. This is a one-time setup.

> **Hermes Agent** and **OpenClaw** both support Feishu via their own gateway implementations (env-var based config, `pip install` / `npm install`). OAB's approach is K8s-native: a single `helm install` deploys everything, with credentials managed as K8s Secrets.

### 1. Create App

1. Go to [Feishu Open Platform](https://open.feishu.cn) (or [Lark Developer](https://open.larksuite.com) for overseas)
2. Click **Create Custom App**
3. Note down the **App ID** (`cli_xxx`) and **App Secret**

### 2. Enable Bot Capability

1. In the left sidebar, go to **App Features** → **Bot**
2. Toggle **Enable Bot** to ON

> ⚠️ This step is easy to miss. Without it, the app cannot receive messages.

### 3. Configure Event Subscription

1. Navigate to **Event Subscriptions** in the left sidebar
2. **Connection mode**: Select **WebSocket** (recommended)
- WebSocket requires no public URL — the gateway connects outbound
- If you must use webhook mode, see [Webhook Mode](#webhook-mode) below
3. **Add event**: `im.message.receive_v1` (Receive messages)

### 4. Add Permissions

Under **Permissions & Scopes**, add these scopes:

| Scope | Purpose |
|-------|---------|
| `im:message` | Send and receive messages |
| `im:message.group_at_msg` | Receive @mention messages in groups |
| `im:message.group_at_msg:readonly` | Read group @mention messages |
| `im:message.p2p_msg:readonly` | Read DM messages |
| `im:resource` | Download images/files from messages |
| `contact:user.base:readonly` | Resolve user display names |

### 5. Publish

Click **Create Version** → **Apply for publish**. For development, you can use the app in test mode without full approval.

### 6. Get IDs for Access Control (Optional)

To restrict which users/groups can interact with the bot:

- **Group ID** (`oc_xxx`): Open the group → click top-right menu → Settings → Group ID
- **User Open ID** (`ou_xxx`): Check gateway logs after the user sends a message, or use the Feishu Contact API

## Credential Management

Three options from simplest to most secure:

| # | Method | Security | Notes |
|---|--------|----------|-------|
| 1 | `--set feishu.appId=X --set feishu.appSecret=Y` | ⚠️ Stored in Helm release | Good for dev/testing |
| 2 | `kubectl create secret` + `--set existingSecret=name` | ✅ Out of Helm values | Good for production |
| 3 | `kubectl create secret --from-env-file=<(vault/aws sm)` + `--set existingSecret=name` | ✅✅ Never touches disk | Best for security |

### Option 2 example:

```bash
kubectl create secret generic feishu-creds -n openab \
--from-literal=feishu-app-id="cli_xxx" \
--from-literal=feishu-app-secret="xxx"

helm install my-bot ./charts/openab-feishu \
--set existingSecret=feishu-creds \
--namespace openab --create-namespace
```

### Option 3 example (AWS Secrets Manager):

```bash
kubectl create secret generic feishu-creds -n openab \
--from-env-file=<(aws secretsmanager get-secret-value \
--secret-id oab-feishu --query SecretString --output text | \
jq -r '{"feishu-app-id": .appId, "feishu-app-secret": .appSecret} | to_entries[] | "\(.key)=\(.value)"')

helm install my-bot ./charts/openab-feishu \
--set existingSecret=feishu-creds \
--namespace openab --create-namespace
```

## Release Channel

| `channel` | Core image tag | Gateway image tag |
|-----------|---------------|-------------------|
| `stable` (default) | `ghcr.io/openabdev/openab:stable` | `v0.5.1` (pinned) |
| `beta` | `ghcr.io/openabdev/openab:beta` | `v0.5.1` (pinned) |

## Webhook Mode

If WebSocket is not available (e.g., network policy blocks outbound WebSocket), switch to webhook mode:

```bash
helm install my-bot ./charts/openab-feishu \
--set feishu.appId="cli_xxx" \
--set feishu.appSecret="xxx" \
--set feishu.connectionMode="webhook" \
--set feishu.verificationToken="xxx" \
--set feishu.encryptKey="xxx" \
--set tunnel.token="eyJ..." \
--set webhookDomain="bot.example.com" \
--namespace openab --create-namespace
```

This adds a cloudflared sidecar (3-container pod, same as `openab-telegram`).

After deployment:
1. Configure Cloudflare Tunnel ingress to point your domain at `localhost:8080`
2. In Feishu Open Platform → Event Subscriptions → set Request URL to `https://bot.example.com/webhook/feishu`
- ⚠️ The gateway must be running when you set the URL — Feishu sends a challenge request immediately

## Lark (Overseas)

For Lark (larksuite.com) instead of Feishu (feishu.cn):

```bash
helm install my-bot ./charts/openab-feishu \
--set feishu.appId="cli_xxx" \
--set feishu.appSecret="xxx" \
--set feishu.domain="lark" \
--namespace openab --create-namespace
```

## Comparison with Other Platforms

| Feature | openab-feishu | openab-telegram | OpenClaw | Hermes Agent |
|---------|--------------|-----------------|----------|--------------|
| Default containers | 2 (agent + gateway) | 3 (+ cloudflared) | N/A (no Helm) | N/A (no Helm) |
| Public endpoint needed | ❌ (WebSocket) | ✅ (webhook) | Varies | Varies |
| Feishu/Lark support | ✅ Native | ❌ | ❌ | ❌ |
| K8s-native deployment | ✅ Helm chart | ✅ Helm chart | ❌ docker-compose | ❌ pip install |
| Credential params | 2 (appId + appSecret) | 2 (botToken + tunnelToken) | N/A | N/A |

## Values Reference

| Key | Default | Description |
|-----|---------|-------------|
| `feishu.appId` | `""` | **(required)** Feishu App ID |
| `feishu.appSecret` | `""` | **(required)** Feishu App Secret |
| `feishu.domain` | `"feishu"` | `"feishu"` or `"lark"` |
| `feishu.connectionMode` | `"websocket"` | `"websocket"` or `"webhook"` |
| `feishu.verificationToken` | `""` | Webhook verification token |
| `feishu.encryptKey` | `""` | Webhook encrypt key |
| `existingSecret` | `""` | Use pre-existing K8s Secret |
| `tunnel.enabled` | `false` | Enable cloudflared sidecar |
| `tunnel.token` | `""` | Cloudflare Tunnel token |
| `webhookDomain` | `""` | Domain for webhook URL |
| `channel` | `"stable"` | `"stable"` or `"beta"` |
| `platform.requireMention` | `true` | Require @mention in groups |
| `platform.allowedGroups` | `[]` | Allowed group chat IDs |
| `platform.allowedUsers` | `[]` | Allowed user open_ids |
| `persistence.enabled` | `true` | Enable PVC for agent state |
| `persistence.size` | `"1Gi"` | PVC size |

## Troubleshooting

| Problem | Fix |
|---------|-----|
| Bot doesn't respond to DMs | Ensure Bot capability is enabled in App Features → Bot |
| Bot doesn't respond in groups | Ensure you @mention the bot (default: `requireMention: true`) |
| Bot doesn't receive any messages | Check event subscription: must have `im.message.receive_v1` and WebSocket mode selected |
| Gateway logs show "token refresh error" | Verify `appId` and `appSecret` are correct |
| Gateway logs show "feishu ws endpoint error" | WebSocket mode requires the app to be published (at least test version) |
| Permission denied on image/file download | Grant `im:resource` scope and re-publish the app |
| User names show as `ou_xxx` | Grant `contact:user.base:readonly` scope |
| Pod CrashLoopBackOff | Check `kubectl logs -c gateway` — usually a credential issue |

## Comparison: OAB vs OpenClaw vs Hermes Agent (Feishu)

| Aspect | OAB (this chart) | OpenClaw | Hermes Agent |
|--------|-----------------|----------|--------------|
| Deployment | `helm install` (K8s-native) | `npx @larksuite/openclaw-lark install` | `hermes gateway setup` |
| Runtime | Rust binary (gateway) + any agent | Node.js | Python |
| Connection mode | WebSocket (default) / Webhook | WebSocket (default) / Webhook | WebSocket (default) / Webhook |
| Config style | Helm values + K8s Secrets | JSON config file | `.env` + `config.yaml` |
| Credential management | 3-tier (--set → K8s Secret → external SM) | Plain config file | `.env` file |
| Security hardening | Non-root, read-only rootfs, drop all caps | N/A (runs as user process) | N/A (runs as user process) |
| Public endpoint needed | ❌ (WebSocket mode) | ❌ (WebSocket mode) | ❌ (WebSocket mode) |
| Feishu-specific features | @mention gating, user allowlist, group allowlist, bot-to-bot, media proxy | Streaming cards, multi-account, ACP sessions, pairing | Interactive cards, document comments, per-group ACL, burst batching |

73 changes: 73 additions & 0 deletions charts/openab-feishu/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
🎉 OpenAB Feishu bot deployed!

{{- if include "openab-feishu.tunnelEnabled" . }}
Pod: {{ include "openab-feishu.fullname" . }} (3 containers: openab, gateway, cloudflared)
Mode: webhook (with Cloudflare Tunnel)
{{- else }}
Pod: {{ include "openab-feishu.fullname" . }} (2 containers: openab, gateway)
Mode: websocket (outbound-only, no public endpoint needed)
{{- end }}

## Post-Install Steps

{{- if eq .Values.feishu.connectionMode "websocket" }}

### Step 1: Configure Feishu Open Platform (one-time)

1. Go to https://open.feishu.cn → Create or select your app
2. Under "Event Subscriptions" → Connection mode: select "WebSocket"
3. Subscribe to event: im.message.receive_v1
4. Under "Permissions", add:
- im:message (Send and receive messages)
- im:message.group_at_msg (Receive group @messages)
- im:resource (Download message resources)
- contact:user.base:readonly (Read user basic info — for display names)
5. Publish the app version

No public URL or webhook configuration needed — the gateway connects outbound.

{{- else }}

### Step 1: Configure Cloudflare Tunnel ingress

Option A — Via Cloudflare Dashboard:
https://one.dash.cloudflare.com/ → Networks → Tunnels → your tunnel → Public Hostname → Add:
Hostname: {{ .Values.webhookDomain | default "bot.example.com" }}
Type: HTTP
URL: localhost:8080

### Step 2: Configure Feishu Open Platform

1. Go to https://open.feishu.cn → Create or select your app
2. Under "Event Subscriptions" → Connection mode: select "HTTP"
3. Set Request URL: https://{{ .Values.webhookDomain | default "YOUR_DOMAIN" }}/webhook/feishu
(The gateway must be running — Feishu sends a challenge request immediately)
4. Subscribe to event: im.message.receive_v1
5. Under "Permissions", add:
- im:message
- im:message.group_at_msg
- im:resource
- contact:user.base:readonly
6. Publish the app version

{{- end }}

### {{ if eq .Values.feishu.connectionMode "websocket" }}Step 2{{ else }}Step 3{{ end }}: Authenticate the agent

The agent needs a one-time OAuth login:

kubectl exec -it deployment/{{ include "openab-feishu.fullname" . }} -n {{ .Release.Namespace }} -c openab -- {{ .Values.agent.command }} login --use-device-flow

Then restart to pick up credentials:

kubectl rollout restart deployment/{{ include "openab-feishu.fullname" . }} -n {{ .Release.Namespace }}

## Verify

Send a message to your bot on Feishu (DM or @mention in a group). Check logs if no response:

kubectl logs -n {{ .Release.Namespace }} deployment/{{ include "openab-feishu.fullname" . }} -c openab --tail=20
kubectl logs -n {{ .Release.Namespace }} deployment/{{ include "openab-feishu.fullname" . }} -c gateway --tail=20
{{- if include "openab-feishu.tunnelEnabled" . }}
kubectl logs -n {{ .Release.Namespace }} deployment/{{ include "openab-feishu.fullname" . }} -c cloudflared --tail=20
{{- end }}
41 changes: 41 additions & 0 deletions charts/openab-feishu/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{{- define "openab-feishu.fullname" -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- end }}

{{- define "openab-feishu.labels" -}}
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 }}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{- define "openab-feishu.selectorLabels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{- define "openab-feishu.agentImage" -}}
{{- $tag := .Values.image.tag -}}
{{- if not $tag -}}
{{- $tag = .Values.channel | default "stable" -}}
{{- end -}}
{{- printf "%s:%s" .Values.image.repository $tag -}}
{{- end }}

{{- define "openab-feishu.gatewayImage" -}}
{{- printf "%s:%s" .Values.gateway.image .Values.gateway.tag -}}
{{- end }}

{{- define "openab-feishu.secretName" -}}
{{- .Values.existingSecret | default (include "openab-feishu.fullname" .) -}}
{{- end }}

{{- define "openab-feishu.tunnelEnabled" -}}
{{- if .Values.tunnel.enabled -}}
true
{{- else if and (eq .Values.feishu.connectionMode "webhook") .Values.tunnel.token -}}
true
{{- else -}}
{{- end -}}
{{- end }}
36 changes: 36 additions & 0 deletions charts/openab-feishu/templates/configmap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "openab-feishu.fullname" . }}
labels:
{{- include "openab-feishu.labels" . | nindent 4 }}
data:
config.toml: |
[agent]
command = {{ .Values.agent.command | toJson }}
args = {{ .Values.agent.args | default list | toJson }}
working_dir = {{ .Values.agent.workingDir | default "/home/agent" | toJson }}
{{- if .Values.agent.env }}
env = { {{ range $i, $k := (.Values.agent.env | keys | sortAlpha) }}{{ if gt $i 0 }}, {{ end }}{{ $k }} = {{ index $.Values.agent.env $k | toJson }}{{ end }} }
{{- end }}
{{- $secretEnvKeys := list }}
{{- range .Values.agent.secretEnv }}{{ $secretEnvKeys = append $secretEnvKeys .name }}{{ end }}
{{- if $secretEnvKeys }}
inherit_env = {{ $secretEnvKeys | toJson }}
{{- end }}

[pool]
max_sessions = {{ .Values.agent.pool.maxSessions | default 10 }}
session_ttl_hours = {{ .Values.agent.pool.sessionTtlHours | default 24 }}

[reactions]
enabled = {{ .Values.agent.reactions.enabled }}
remove_after_reply = {{ .Values.agent.reactions.removeAfterReply }}

[gateway]
url = "ws://localhost:8080/ws"
platform = "feishu"
allow_all_channels = {{ if .Values.platform.allowedGroups }}false{{ else }}true{{ end }}
allowed_channels = {{ .Values.platform.allowedGroups | default list | toJson }}
allow_all_users = {{ if .Values.platform.allowedUsers }}false{{ else }}true{{ end }}
allowed_users = {{ .Values.platform.allowedUsers | default list | toJson }}
Loading
Loading