- 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 辅助工具
122 lines
5.0 KiB
Docker
122 lines
5.0 KiB
Docker
# 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.1,CSRF 校验通过,同时 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.1,CSRF 校验依旧通过)
|
||
# 与 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. 后台起 socat,0.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\
|
||
"]
|