feat(feishu): carry Lighthouse bridge into v0.8.37

Add the Feishu/Lark long-connection bridge, Tencent Lighthouse runbooks, CNB mirror guidance, CNB tag release pipeline, and China-friendly update fallback documentation for the v0.8.37 line.
This commit is contained in:
Hunter Bown
2026-05-14 03:56:03 -05:00
committed by GitHub
parent 019d55694a
commit 9483248a9f
32 changed files with 3795 additions and 35 deletions
+134
View File
@@ -0,0 +1,134 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ "${EUID}" -ne 0 ]]; then
echo "Run as root: sudo bash scripts/tencent-lighthouse/bootstrap-ubuntu.sh" >&2
exit 1
fi
DEEPSEEK_USER="${DEEPSEEK_USER:-deepseek}"
DEEPSEEK_ROOT="${DEEPSEEK_ROOT:-/opt/deepseek}"
WHALEBRO_ROOT="${WHALEBRO_ROOT:-/opt/whalebro}"
REPO_URL="${DEEPSEEK_REPO_URL:-https://github.com/Hmbown/DeepSeek-TUI.git}"
WHALEBRO_EXTRA_REPOS="${WHALEBRO_EXTRA_REPOS:-}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SOURCE_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
SOURCE_BRANCH="$(git -C "${SOURCE_ROOT}" branch --show-current 2>/dev/null || true)"
REPO_BRANCH="${DEEPSEEK_REPO_BRANCH:-${SOURCE_BRANCH:-main}}"
apt-get update
apt-get install -y \
ca-certificates \
curl \
git \
iproute2 \
openssh-client \
build-essential \
pkg-config \
libssl-dev \
nodejs \
npm \
rsync \
tmux \
fail2ban \
ufw
node_major="$(node -p "Number(process.versions.node.split('.')[0])")"
if (( node_major < 18 )); then
echo "Node.js 18+ is required for the Feishu bridge; install a newer Node.js before running install-services.sh." >&2
fi
if ! id -u "${DEEPSEEK_USER}" >/dev/null 2>&1; then
useradd --create-home --shell /bin/bash "${DEEPSEEK_USER}"
fi
install -d -o "${DEEPSEEK_USER}" -g "${DEEPSEEK_USER}" "${DEEPSEEK_ROOT}"
install -d -o "${DEEPSEEK_USER}" -g "${DEEPSEEK_USER}" "${DEEPSEEK_ROOT}/bridge"
install -d -o "${DEEPSEEK_USER}" -g "${DEEPSEEK_USER}" "${WHALEBRO_ROOT}"
install -d -o "${DEEPSEEK_USER}" -g "${DEEPSEEK_USER}" "${WHALEBRO_ROOT}/worktrees"
install -d -m 0750 -o root -g "${DEEPSEEK_USER}" /etc/deepseek
install -d -m 0700 -o "${DEEPSEEK_USER}" -g "${DEEPSEEK_USER}" /var/lib/deepseek-feishu-bridge
if [[ ! -d "${WHALEBRO_ROOT}/deepseek-tui/.git" ]]; then
sudo -u "${DEEPSEEK_USER}" git clone --branch "${REPO_BRANCH}" "${REPO_URL}" "${WHALEBRO_ROOT}/deepseek-tui"
fi
for repo_spec in ${WHALEBRO_EXTRA_REPOS}; do
repo_name="${repo_spec%%=*}"
repo_url="${repo_spec#*=}"
if [[ -z "${repo_name}" || -z "${repo_url}" || "${repo_name}" == "${repo_url}" ]]; then
echo "Skipping malformed WHALEBRO_EXTRA_REPOS entry: ${repo_spec}" >&2
continue
fi
if [[ ! -d "${WHALEBRO_ROOT}/${repo_name}/.git" ]]; then
sudo -u "${DEEPSEEK_USER}" git clone "${repo_url}" "${WHALEBRO_ROOT}/${repo_name}" || {
echo "Warning: failed to clone optional repo ${repo_name} from ${repo_url}" >&2
}
fi
done
if [[ ! -f "${WHALEBRO_ROOT}/AGENTS.md" && -f "${SOURCE_ROOT}/deploy/tencent-lighthouse/examples/whalebro.AGENTS.md" ]]; then
install -m 0644 -o "${DEEPSEEK_USER}" -g "${DEEPSEEK_USER}" \
"${SOURCE_ROOT}/deploy/tencent-lighthouse/examples/whalebro.AGENTS.md" \
"${WHALEBRO_ROOT}/AGENTS.md"
fi
if [[ ! -f /etc/deepseek/runtime.env ]]; then
cat >/etc/deepseek/runtime.env <<'EOF'
DEEPSEEK_RUNTIME_TOKEN=replace-with-long-random-token
DEEPSEEK_RUNTIME_PORT=7878
DEEPSEEK_RUNTIME_WORKERS=2
DEEPSEEK_API_KEY=replace-with-deepseek-platform-key
RUST_LOG=info
EOF
chown root:"${DEEPSEEK_USER}" /etc/deepseek/runtime.env
chmod 0640 /etc/deepseek/runtime.env
fi
if [[ ! -f /etc/deepseek/feishu-bridge.env ]]; then
cat >/etc/deepseek/feishu-bridge.env <<'EOF'
FEISHU_APP_ID=cli_xxxxxxxxxxxxxxxx
FEISHU_APP_SECRET=replace-with-app-secret
FEISHU_DOMAIN=feishu
DEEPSEEK_RUNTIME_URL=http://127.0.0.1:7878
DEEPSEEK_RUNTIME_TOKEN=replace-with-same-token-as-runtime-env
DEEPSEEK_WORKSPACE=/opt/whalebro
DEEPSEEK_MODEL=auto
DEEPSEEK_MODE=agent
DEEPSEEK_ALLOW_SHELL=true
DEEPSEEK_TRUST_MODE=false
DEEPSEEK_AUTO_APPROVE=false
DEEPSEEK_CHAT_ALLOWLIST=
DEEPSEEK_ALLOW_UNLISTED=false
FEISHU_THREAD_MAP_PATH=/var/lib/deepseek-feishu-bridge/thread-map.json
FEISHU_ALLOW_GROUPS=false
FEISHU_REQUIRE_PREFIX_IN_GROUP=true
FEISHU_GROUP_PREFIX=/ds
FEISHU_MAX_REPLY_CHARS=3500
DEEPSEEK_TURN_TIMEOUT_MS=900000
EOF
chown root:"${DEEPSEEK_USER}" /etc/deepseek/feishu-bridge.env
chmod 0640 /etc/deepseek/feishu-bridge.env
fi
ufw allow OpenSSH
ufw --force enable
cat <<EOF
Base server setup complete.
Next:
1. Install Rust 1.88+ for ${DEEPSEEK_USER}; rustup is the usual path.
2. Build/install both binaries:
sudo -iu ${DEEPSEEK_USER}
cd ${WHALEBRO_ROOT}/deepseek-tui
cargo install --path crates/cli --locked --force
cargo install --path crates/tui --locked --force
3. Copy integrations/feishu-bridge to ${DEEPSEEK_ROOT}/bridge and run npm install.
4. Edit /etc/deepseek/runtime.env and /etc/deepseek/feishu-bridge.env.
5. Install systemd units with scripts/tencent-lighthouse/install-services.sh.
6. After the env files are edited and services are started, run:
sudo bash scripts/tencent-lighthouse/doctor.sh
EOF
+306
View File
@@ -0,0 +1,306 @@
#!/usr/bin/env bash
set -euo pipefail
DEEPSEEK_USER="${DEEPSEEK_USER:-deepseek}"
DEEPSEEK_ROOT="${DEEPSEEK_ROOT:-/opt/deepseek}"
WHALEBRO_ROOT="${WHALEBRO_ROOT:-/opt/whalebro}"
RUNTIME_ENV="${RUNTIME_ENV:-/etc/deepseek/runtime.env}"
BRIDGE_ENV="${BRIDGE_ENV:-/etc/deepseek/feishu-bridge.env}"
BRIDGE_DIR="${BRIDGE_DIR:-${DEEPSEEK_ROOT}/bridge}"
REPO_ROOT="${REPO_ROOT:-${WHALEBRO_ROOT}/deepseek-tui}"
failures=0
warnings=0
section() {
printf '\n== %s ==\n' "$1"
}
pass() {
printf '[ok] %s\n' "$1"
}
warn() {
warnings=$((warnings + 1))
printf '[warn] %s\n' "$1"
}
fail() {
failures=$((failures + 1))
printf '[fail] %s\n' "$1"
}
have_command() {
command -v "$1" >/dev/null 2>&1
}
env_value() {
local file="$1"
local key="$2"
[[ -f "${file}" ]] || return 0
grep -E "^[[:space:]]*(export[[:space:]]+)?${key}=" "${file}" \
| tail -n 1 \
| sed -E "s/^[[:space:]]*(export[[:space:]]+)?${key}=//; s/^[[:space:]]+//; s/[[:space:]]+$//; s/^['\"]//; s/['\"]$//" \
|| true
}
is_placeholder() {
local value
value="$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')"
[[ -z "${value}" || "${value}" == *replace-with* || "${value}" == *xxxxxxxx* || "${value}" == "changeme" ]]
}
file_mode() {
if stat -c '%a' "$1" >/dev/null 2>&1; then
stat -c '%a' "$1"
else
stat -f '%Lp' "$1"
fi
}
check_commands() {
section "Runtime tools"
for cmd in git curl node npm systemctl ss; do
if have_command "${cmd}"; then
pass "${cmd} is installed"
else
warn "${cmd} is not on PATH"
fi
done
}
check_node() {
section "Node"
if ! have_command node; then
fail "node is required for the Feishu bridge"
return
fi
local major
major="$(node -p "Number(process.versions.node.split('.')[0])" 2>/dev/null || echo 0)"
if [[ "${major}" =~ ^[0-9]+$ ]] && (( major >= 18 )); then
pass "Node.js major version is ${major}"
else
fail "Node.js 18+ is required; found ${major}"
fi
}
check_workspace() {
section "Workspace"
[[ -d "${WHALEBRO_ROOT}" ]] && pass "${WHALEBRO_ROOT} exists" || fail "${WHALEBRO_ROOT} is missing"
[[ -d "${REPO_ROOT}/.git" ]] && pass "${REPO_ROOT} is a git checkout" || fail "${REPO_ROOT} is not a git checkout"
[[ -d "${WHALEBRO_ROOT}/worktrees" ]] && pass "${WHALEBRO_ROOT}/worktrees exists" || warn "${WHALEBRO_ROOT}/worktrees is missing"
if [[ -f "${WHALEBRO_ROOT}/AGENTS.md" ]]; then
pass "${WHALEBRO_ROOT}/AGENTS.md exists"
else
warn "${WHALEBRO_ROOT}/AGENTS.md is missing"
fi
}
check_binaries() {
section "DeepSeek binaries"
local cargo_bin="/home/${DEEPSEEK_USER}/.cargo/bin"
local deepseek="${cargo_bin}/deepseek"
local tui="${cargo_bin}/deepseek-tui"
if [[ -x "${deepseek}" ]]; then
pass "${deepseek} is executable"
"${deepseek}" --version 2>/dev/null | sed 's/^/[info] deepseek version: /' || warn "deepseek --version failed"
else
fail "${deepseek} is missing or not executable"
fi
if [[ -x "${tui}" ]]; then
pass "${tui} is executable"
"${tui}" --version 2>/dev/null | sed 's/^/[info] deepseek-tui version: /' || warn "deepseek-tui --version failed"
else
fail "${tui} is missing or not executable"
fi
}
check_env_file() {
local file="$1"
local label="$2"
if [[ ! -f "${file}" ]]; then
fail "${label} env file is missing: ${file}"
return
fi
pass "${label} env file exists"
local mode
mode="$(file_mode "${file}")"
local world="${mode: -1}"
if [[ "${world}" =~ ^[0-9]+$ ]] && (( world > 0 )); then
fail "${label} env file is world-readable (${mode})"
else
pass "${label} env file is not world-readable (${mode})"
fi
}
check_env() {
section "Environment"
check_env_file "${RUNTIME_ENV}" "runtime"
check_env_file "${BRIDGE_ENV}" "bridge"
local runtime_token bridge_token api_key workspace domain allow_groups allow_unlisted
runtime_token="$(env_value "${RUNTIME_ENV}" DEEPSEEK_RUNTIME_TOKEN)"
bridge_token="$(env_value "${BRIDGE_ENV}" DEEPSEEK_RUNTIME_TOKEN)"
api_key="$(env_value "${RUNTIME_ENV}" DEEPSEEK_API_KEY)"
workspace="$(env_value "${BRIDGE_ENV}" DEEPSEEK_WORKSPACE)"
domain="$(env_value "${BRIDGE_ENV}" FEISHU_DOMAIN)"
allow_groups="$(env_value "${BRIDGE_ENV}" FEISHU_ALLOW_GROUPS)"
allow_unlisted="$(env_value "${BRIDGE_ENV}" DEEPSEEK_ALLOW_UNLISTED)"
if is_placeholder "${runtime_token}"; then
fail "runtime DEEPSEEK_RUNTIME_TOKEN is missing or still a placeholder"
else
pass "runtime token is set"
fi
if is_placeholder "${bridge_token}"; then
fail "bridge DEEPSEEK_RUNTIME_TOKEN is missing or still a placeholder"
else
pass "bridge token is set"
fi
if [[ -n "${runtime_token}" && -n "${bridge_token}" && "${runtime_token}" != "${bridge_token}" ]]; then
fail "runtime and bridge tokens do not match"
elif [[ -n "${runtime_token}" && -n "${bridge_token}" ]]; then
pass "runtime and bridge tokens match"
fi
if is_placeholder "${api_key}"; then
warn "DEEPSEEK_API_KEY is missing or still a placeholder"
else
pass "DEEPSEEK_API_KEY is set"
fi
[[ "${workspace}" == "${WHALEBRO_ROOT}" || "${workspace}" == "${WHALEBRO_ROOT}/"* ]] \
&& pass "bridge workspace is under ${WHALEBRO_ROOT}" \
|| warn "bridge workspace is outside ${WHALEBRO_ROOT}: ${workspace:-unset}"
[[ "${domain:-feishu}" == "feishu" || "${domain:-feishu}" == "lark" || "${domain:-feishu}" == https://open.* ]] \
&& pass "FEISHU_DOMAIN is ${domain:-feishu}" \
|| fail "FEISHU_DOMAIN must be feishu, lark, or an https://open.* URL"
[[ "${allow_groups:-false}" == "true" && "${allow_unlisted:-false}" == "true" ]] \
&& fail "group control cannot run with DEEPSEEK_ALLOW_UNLISTED=true" \
|| pass "group/unlisted mode is not openly combined"
}
check_validator() {
section "Bridge config validator"
local validator="${BRIDGE_DIR}/scripts/validate-config.mjs"
if [[ ! -f "${validator}" ]]; then
validator="${REPO_ROOT}/integrations/feishu-bridge/scripts/validate-config.mjs"
fi
if [[ ! -f "${validator}" ]]; then
warn "bridge config validator is not installed"
return
fi
local runner=(node)
if [[ "${EUID}" -eq 0 ]] && id -u "${DEEPSEEK_USER}" >/dev/null 2>&1 && have_command sudo; then
runner=(sudo -u "${DEEPSEEK_USER}" node)
fi
if "${runner[@]}" "${validator}" --env "${BRIDGE_ENV}" --runtime-env "${RUNTIME_ENV}" --workspace-root "${WHALEBRO_ROOT}" --check-filesystem; then
pass "bridge config validator passed"
else
fail "bridge config validator reported blocking issues"
fi
}
check_systemd() {
section "systemd"
if ! have_command systemctl || [[ ! -d /run/systemd/system ]]; then
warn "systemd is not available in this environment"
return
fi
for unit in deepseek-runtime deepseek-feishu-bridge; do
[[ -f "/etc/systemd/system/${unit}.service" ]] \
&& pass "${unit}.service is installed" \
|| fail "${unit}.service is missing"
systemctl is-enabled --quiet "${unit}" \
&& pass "${unit} is enabled" \
|| warn "${unit} is not enabled"
systemctl is-active --quiet "${unit}" \
&& pass "${unit} is active" \
|| fail "${unit} is not active"
done
}
check_bridge_install() {
section "Bridge install"
[[ -f "${BRIDGE_DIR}/package.json" ]] && pass "${BRIDGE_DIR}/package.json exists" || fail "bridge package.json is missing"
[[ -f "${BRIDGE_DIR}/src/index.mjs" ]] && pass "${BRIDGE_DIR}/src/index.mjs exists" || fail "bridge entrypoint is missing"
if [[ -d "${BRIDGE_DIR}/node_modules/@larksuiteoapi/node-sdk" ]]; then
pass "Lark SDK dependency is installed"
else
warn "Lark SDK dependency is not installed under ${BRIDGE_DIR}/node_modules"
fi
}
check_localhost_health() {
section "Localhost health"
local port token
port="$(env_value "${RUNTIME_ENV}" DEEPSEEK_RUNTIME_PORT)"
port="${port:-7878}"
token="$(env_value "${BRIDGE_ENV}" DEEPSEEK_RUNTIME_TOKEN)"
if have_command ss; then
local listeners
listeners="$(ss -ltn 2>/dev/null | awk -v port=":${port}" '$4 ~ port {print $4}' || true)"
if grep -qE "^127\\.0\\.0\\.1:${port}$|^\\[::1\\]:${port}$" <<<"${listeners}"; then
pass "runtime port ${port} is bound to localhost"
elif [[ -n "${listeners}" ]]; then
fail "runtime port ${port} is listening on a non-local address: ${listeners//$'\n'/, }"
else
fail "runtime port ${port} is not listening"
fi
else
warn "ss is unavailable; skipping bind-address check"
fi
if ! have_command curl; then
warn "curl is unavailable; skipping HTTP checks"
return
fi
if curl -fsS --max-time 3 "http://127.0.0.1:${port}/health" >/dev/null; then
pass "/health responds on localhost"
else
fail "/health did not respond on localhost:${port}"
fi
if is_placeholder "${token}"; then
warn "runtime token is not usable; skipping /v1/runtime/info auth check"
return
fi
local tmp
tmp="$(mktemp)"
if curl -fsS --max-time 3 -H "Authorization: Bearer ${token}" \
"http://127.0.0.1:${port}/v1/runtime/info" >"${tmp}"; then
if node -e '
const fs = require("fs");
const data = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
if (data.bind_host !== "127.0.0.1") process.exit(2);
if (data.auth_required !== true) process.exit(3);
' "${tmp}"; then
pass "/v1/runtime/info reports localhost bind and auth_required=true"
else
fail "/v1/runtime/info did not report localhost bind with auth enabled"
fi
else
fail "/v1/runtime/info did not respond with bearer auth"
fi
rm -f "${tmp}"
}
main() {
printf 'Tencent Lighthouse DeepSeek doctor\n'
check_commands
check_node
check_workspace
check_binaries
check_env
check_bridge_install
check_validator
check_systemd
check_localhost_health
section "Summary"
printf '%s failure(s), %s warning(s)\n' "${failures}" "${warnings}"
(( failures == 0 ))
}
main "$@"
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ "${EUID}" -ne 0 ]]; then
echo "Run as root: sudo bash scripts/tencent-lighthouse/install-services.sh" >&2
exit 1
fi
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
DEEPSEEK_USER="${DEEPSEEK_USER:-deepseek}"
DEEPSEEK_ROOT="${DEEPSEEK_ROOT:-/opt/deepseek}"
install -d -o "${DEEPSEEK_USER}" -g "${DEEPSEEK_USER}" "${DEEPSEEK_ROOT}/bridge"
rsync -a --delete \
--exclude node_modules \
"${REPO_ROOT}/integrations/feishu-bridge/" \
"${DEEPSEEK_ROOT}/bridge/"
chown -R "${DEEPSEEK_USER}:${DEEPSEEK_USER}" "${DEEPSEEK_ROOT}/bridge"
if [[ -f "${DEEPSEEK_ROOT}/bridge/package-lock.json" ]]; then
sudo -u "${DEEPSEEK_USER}" npm --prefix "${DEEPSEEK_ROOT}/bridge" ci --omit=dev
else
sudo -u "${DEEPSEEK_USER}" npm --prefix "${DEEPSEEK_ROOT}/bridge" install --omit=dev
fi
install -m 0644 "${REPO_ROOT}/deploy/tencent-lighthouse/systemd/deepseek-runtime.service" /etc/systemd/system/deepseek-runtime.service
install -m 0644 "${REPO_ROOT}/deploy/tencent-lighthouse/systemd/deepseek-feishu-bridge.service" /etc/systemd/system/deepseek-feishu-bridge.service
systemctl daemon-reload
systemctl enable deepseek-runtime deepseek-feishu-bridge
cat <<'EOF'
Services installed but not started.
Before starting, verify:
/etc/deepseek/runtime.env
/etc/deepseek/feishu-bridge.env
sudo -u deepseek node /opt/deepseek/bridge/scripts/validate-config.mjs --env /etc/deepseek/feishu-bridge.env --runtime-env /etc/deepseek/runtime.env --workspace-root /opt/whalebro --check-filesystem
Then run:
sudo systemctl start deepseek-runtime
sudo systemctl start deepseek-feishu-bridge
sudo bash /opt/whalebro/deepseek-tui/scripts/tencent-lighthouse/doctor.sh
sudo journalctl -u deepseek-feishu-bridge -f
EOF