sub2api/deploy/Dockerfile.ls
win 002066e700 chore(wip): 保存订制改动以便合并上游
- windsurf: client/pool/local_ls/tool_emulation/tool_names/models 调整
- handler: admin account_data / failover_loop / gateway_handler
- repository: scheduler_cache 及测试
- service: windsurf_chat_service / windsurf_gateway_service
- deploy: compose 合并为单文件(含 windsurf-ls profile),Dockerfile.ls
- cmd: 新增 dump_ls_models / dump_preamble / test_windsurf_tools 辅助工具
2026-04-24 11:14:36 +08:00

122 lines
5.0 KiB
Docker
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Windsurf Language Server Docker Image
#
# 说明:
# - LS 本体只监听 127.0.0.1:<LS_INTERNAL_PORT>,并且仅对 loopback peer 通过 CSRF 校验。
# - 为了让 LS 融入 compose 内部网络(而不是必须使用 host network
# 容器内启动一个 socat 把外部 0.0.0.0:<LS_PORT> 的流量转发到 127.0.0.1:<LS_INTERNAL_PORT>。
# LS 收到的 peer 地址仍然是 127.0.0.1CSRF 校验通过,同时 compose 里其它服务
# 可以直接用 `windsurf-ls:42099` 访问。
#
# 构建:
# docker build -t windsurf-ls -f deploy/Dockerfile.ls .
#
# 运行(一般不要单独 docker run通过 compose 的 windsurf profile 启动):
# docker compose --profile windsurf up -d
#
# LS 二进制在构建时从 Exafunction/codeium 的 latest release 下载。
# 本地已有二进制时可通过 --build-arg LS_URL=file:///path 覆盖。
FROM alpine:3.21 AS downloader
RUN apk add --no-cache curl jq
ARG TARGETARCH
ARG LS_URL=""
RUN set -e; \
if [ -n "$LS_URL" ]; then \
echo "Downloading LS from: $LS_URL"; \
curl -fL --progress-bar -o /tmp/language_server "$LS_URL"; \
else \
case "$TARGETARCH" in \
amd64) ASSET="language_server_linux_x64" ;; \
arm64) ASSET="language_server_linux_arm" ;; \
*) echo "Unsupported arch: $TARGETARCH"; exit 1 ;; \
esac; \
echo "Fetching latest Exafunction/codeium release..."; \
URL=$(curl -fsSL https://api.github.com/repos/Exafunction/codeium/releases/latest \
| jq -r --arg asset "$ASSET" '.assets[] | select(.name == $asset) | .browser_download_url'); \
if [ -z "$URL" ] || [ "$URL" = "null" ]; then \
echo "ERROR: Could not find asset $ASSET in latest release"; exit 1; \
fi; \
echo "Downloading: $URL"; \
curl -fL --progress-bar -o /tmp/language_server "$URL"; \
fi; \
chmod +x /tmp/language_server
FROM debian:bookworm-slim
# ca-certificates: LS 访问上游 API (HTTPS)
# netcat-openbsd : healthcheck 用的 `nc -z` 探测端口
# socat : loopback 端口转发,让 compose 内部网络可直达 LS
# tini : PID 1 init正确回收 LS 子进程,转发信号
# bash : entrypoint 依赖 `wait -n`dash/busybox 不支持)
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates netcat-openbsd socat tini bash && \
rm -rf /var/lib/apt/lists/*
WORKDIR /opt/windsurf
COPY --from=downloader /tmp/language_server /opt/windsurf/language_server_linux_x64
RUN mkdir -p /data/db
# LS_PORT : 容器对外暴露的监听端口socat 绑定 0.0.0.0:LS_PORT
# LS_INTERNAL_PORT : LS 本体绑定的端口LS 实际在 0.0.0.0:LS_INTERNAL_PORT 监听,
# 但 socat 发起的连接源地址为 127.0.0.1CSRF 校验依旧通过)
# 与 LS_PORT 必须不同,否则 socat 会和 LS 抢同一端口。
ENV LS_PORT=42099 \
LS_INTERNAL_PORT=42098 \
LS_CSRF_TOKEN=ad2d9f01-4e7b-8c3a-b5f6-1d8e9a0c7b2f \
LS_API_SERVER_URL=https://server.self-serve.windsurf.com \
HTTPS_PROXY="" \
HTTP_PROXY=""
EXPOSE 42099
# 健康检查: socat 端口可达即视为健康(实际会触发一次 TCP 握手到 LS
HEALTHCHECK --interval=10s --timeout=3s --start-period=15s --retries=5 \
CMD nc -z 127.0.0.1 "${LS_PORT}" || exit 1
# tini 做 PID 1确保 LS 子进程被正确收尾 + 信号转发。
# 用 bash 而非 /bin/sh因为 Debian 的 /bin/sh 指向 dash不支持 `wait -n`。
# 启动脚本逻辑:
# 1. 后台拉起 LS只绑 127.0.0.1:${LS_INTERNAL_PORT}
# 2. 轮询等待 LS 真正开始监听
# 3. 后台起 socat0.0.0.0:${LS_PORT} → 127.0.0.1:${LS_INTERNAL_PORT}
# 4. `wait -n` 等任一子进程退出 → 容器一并退出,交由 compose 重启策略兜底
ENTRYPOINT ["/usr/bin/tini", "-g", "--", "/bin/bash", "-c", "\
set -e; \
/opt/windsurf/language_server_linux_x64 \
--api_server_url=\"${LS_API_SERVER_URL}\" \
--server_port=\"${LS_INTERNAL_PORT}\" \
--csrf_token=\"${LS_CSRF_TOKEN}\" \
--register_user_url=https://api.codeium.com/register_user/ \
--codeium_dir=/data \
--database_dir=/data/db \
--enable_local_search=false \
--enable_index_service=false \
--enable_lsp=false \
--detect_proxy=false & \
LS_PID=$!; \
echo \"[entrypoint] LS started pid=$LS_PID, waiting on 127.0.0.1:${LS_INTERNAL_PORT}\"; \
for i in $(seq 1 60); do \
if nc -z 127.0.0.1 \"${LS_INTERNAL_PORT}\"; then \
echo \"[entrypoint] LS is listening, starting socat forwarder\"; \
break; \
fi; \
if ! kill -0 $LS_PID 2>/dev/null; then \
echo \"[entrypoint] LS exited before listening\"; exit 1; \
fi; \
sleep 1; \
done; \
socat -d TCP-LISTEN:${LS_PORT},fork,reuseaddr,bind=0.0.0.0 TCP:127.0.0.1:${LS_INTERNAL_PORT} & \
SOCAT_PID=$!; \
echo \"[entrypoint] socat started pid=$SOCAT_PID, forwarding 0.0.0.0:${LS_PORT} -> 127.0.0.1:${LS_INTERNAL_PORT}\"; \
wait -n $LS_PID $SOCAT_PID; \
EXIT=$?; \
echo \"[entrypoint] one of LS/socat exited with $EXIT, tearing down\"; \
kill $LS_PID $SOCAT_PID 2>/dev/null || true; \
exit $EXIT\
"]