[Bug] Qoder 无法在隔离服务器上完成 SSH 连接:缺少 Local Server Download 机制

Issue Description

在使用 Qoder 通过 SSH 连接一台隔离主机(不可访问公网)时,连接卡在:

Installing SSH server...
Starting download...

经过对比测试:

  • VSCodeCursor 在同样场景下可以正常工作

  • 原因是它们均实现了

    Remote-SSH: Local Server Download(在本地下载 server 并通过 SSH 上传到远程)

  • Qoder 没有实现这个 Local Server Download 机制,导致需要从远程服务器访问外网下载 Qoder Server,但隔离环境下无法访问互联网 → 永久卡住

因此这是 Qoder Connect 中 Remote SSH 的一个缺陷。

Steps to Reproduce

  1. 准备一台无法访问互联网的服务器(如内网 HPC / 隔离集群)

  2. 本地运行 Qoder → 打开 “SSH Remote Connect”

  3. 输入服务器配置并连接

  4. Qoder 进入 “Installing SSH server…” 阶段并卡住

  5. 几秒后客户端弹出 “Network Error”,无法继续

Expected Behavior

当目标服务器不能访问公网时,Qoder 应自动:

  1. 在本地下载对应版本的 qoder-server(tar.gz)

  2. 通过已有 SSH 会话上传到远程的 ~/.qoder-server/bin//

  3. 由远端本地解压、启动 qoder-server

即等价于 VSCode 的:

"remote.SSH.localServerDownload" = "always"

这让用户可以在隔离环境中正常远程开发。

Actual Behavior

  • Qoder 仅支持远程端在线下载 server

  • 当远程服务器不能访问公网 → 无法下载 → 无法安装 → 连接被断开

  • 目录结构卡在:

~/.qoder-server/bin/<hash>/qoder-server.tar.gz (0 bytes)

Screenshots / Screen Recordings

Operating System

  • 本地 OS:macOS 15.7

  • 远程 OS:Ubuntu 20

Current Qoder Version (Menu → About Qoder → Copy)

感谢反馈,请问您的Qoder目前版本是多少?:grinning_face: 目前我们0.2.20 是支持这个功能的

Thank you for your quick reply!

版本: 0.2.20

VSCode Version: 1.100.0

提交: 41eefc9d6820b32df7892471dc26c255bf238b67

日期: 2025-12-03T09:55:14.103Z (1 周前)

Electron: 37.7.0

Chromium: 138.0.7204.251

Node.js: 22.20.0

V8: 13.8.258.32-electron.0

OS: Darwin arm64 24.6.0

如上图所见,搜索关键词download、local server或者localserver,都没有出现localServerDownload的相关选项,或许我应该搜索其他关键字?您可以告知一下如何开启这个功能吗?感谢~

我们这块不用配置的,您这个问题可以提交下问题报告,我们看下日志 :grinning_face:

reported,

ID: 53f07df8-1450-4db0-892b-3d775cad8348

好的,感谢反馈,问题已经收到,我们正在尽快排查和分析,有进展第一时间这里同步您 :grinning_face:

能截图看下么 我们刚才看了日志 看着好像是连成功了 :grinning_face:

Log中的报错主要是无法curl或者wget下载相关资源:

[Trace  - 04:36:04.255] Server install command:

# Server installation script


TMP_DIR="${XDG_RUNTIME_DIR:-"/tmp"}"

DISTRO_VERSION="0.2.20"
DISTRO_COMMIT="41eefc9d6820b32df7892471dc26c255bf238b67"
DISTRO_QUALITY="stable"
DISTRO_VSCODIUM_RELEASE=""

SERVER_APP_NAME="qoder-server"
SERVER_INITIAL_EXTENSIONS=""
SERVER_LISTEN_FLAG="--port=0"
SERVER_DATA_DIR="$HOME/.qoder-server"
SERVER_DIR="$SERVER_DATA_DIR/bin/$DISTRO_COMMIT"
SERVER_SCRIPT="$SERVER_DIR/bin/$SERVER_APP_NAME"
SERVER_LOGFILE="$SERVER_DATA_DIR/.$DISTRO_COMMIT.log"
SERVER_PIDFILE="$SERVER_DATA_DIR/.$DISTRO_COMMIT.pid"
SERVER_TOKENFILE="$SERVER_DATA_DIR/.$DISTRO_COMMIT.token"
SERVER_ARCH=
SERVER_CONNECTION_TOKEN=
SERVER_DOWNLOAD_URL=

LISTENING_ON=
OS_RELEASE_ID=
ARCH=
PLATFORM=
SERVER_STARTUP_TIME=

# Mimic output from logs of remote-ssh extension
print_install_results_and_exit() {
	echo "e2aee38d375864cbd24bb5f8: start"
	echo "exitCode==$1=="
	echo "listeningOn==$LISTENING_ON=="
	echo "connectionToken==$SERVER_CONNECTION_TOKEN=="
	echo "logFile==$SERVER_LOGFILE=="
	echo "osReleaseId==$OS_RELEASE_ID=="
	echo "arch==$ARCH=="
	echo "platform==$PLATFORM=="
	echo "tmpDir==$TMP_DIR=="
	echo "serverStartupTime==$SERVER_STARTUP_TIME=="
	
	echo "e2aee38d375864cbd24bb5f8: end"
	exit 0
}

# Check if platform is supported
KERNEL="$(uname -s)"
case $KERNEL in
	Darwin)
		PLATFORM="darwin"
		;;
	Linux)
		PLATFORM="linux"
		;;
	FreeBSD)
		PLATFORM="freebsd"
		;;
	DragonFly)
		PLATFORM="dragonfly"
		;;
	*)
		echo "Error platform not supported: $KERNEL"
		print_install_results_and_exit 1
		;;
esac

# Check machine architecture
ARCH="$(uname -m)"
case $ARCH in
	x86_64 | amd64)
		SERVER_ARCH="x64"
		;;
	armv7l | armv8l)
		SERVER_ARCH="armhf"
		;;
	arm64 | aarch64)
		SERVER_ARCH="arm64"
		;;
	ppc64le)
		SERVER_ARCH="ppc64le"
		;;
	riscv64)
		SERVER_ARCH="riscv64"
		;;
	loongarch64)
		SERVER_ARCH="loong64"
		;;
	s390x)
		SERVER_ARCH="s390x"
		;;
	*)
		echo "Error architecture not supported: $ARCH"
		print_install_results_and_exit 1
		;;
esac

# https://www.freedesktop.org/software/systemd/man/os-release.html
OS_RELEASE_ID="$(grep -i '^ID=' /etc/os-release 2>/dev/null | sed 's/^ID=//gi' | sed 's/"//g')"
if [[ -z $OS_RELEASE_ID ]]; then
	OS_RELEASE_ID="$(grep -i '^ID=' /usr/lib/os-release 2>/dev/null | sed 's/^ID=//gi' | sed 's/"//g')"
	if [[ -z $OS_RELEASE_ID ]]; then
		OS_RELEASE_ID="unknown"
	fi
fi

# Create installation folder
if [[ ! -d $SERVER_DIR ]]; then
	mkdir -p $SERVER_DIR
	if (( $? > 0 )); then
		echo "Error creating server install directory"
		print_install_results_and_exit 1
	fi
fi

# adjust platform for vscodium download, if needed
if [[ $OS_RELEASE_ID = alpine ]]; then
	PLATFORM=$OS_RELEASE_ID
fi

SERVER_DOWNLOAD_URL="$(echo "https://download.qoder.com/server/\${version}/qoder-reh-\${os}-\${arch}-\${version}.tar.gz" | sed "s/\${quality}/$DISTRO_QUALITY/g" | sed "s/\${version}/$DISTRO_VERSION/g" | sed "s/\${commit}/$DISTRO_COMMIT/g" | sed "s/\${os}/$PLATFORM/g" | sed "s/\${arch}/$SERVER_ARCH/g" | sed "s/\${release}/$DISTRO_VSCODIUM_RELEASE/g")"

# Download URL will be shown by the download command below

# Check if server script is already installed
if [[ ! -f $SERVER_SCRIPT ]]; then
	case "$PLATFORM" in
		darwin | linux | alpine )
			;;
		*)
			echo "Error '$PLATFORM' needs manual installation of remote extension host"
			print_install_results_and_exit 1
			;;
	esac

	pushd $SERVER_DIR > /dev/null

	# Enhanced download with better timeout and retry settings + fallback mechanism
	DOWNLOAD_SUCCESS=1
	HAS_WGET=$(which wget 2>/dev/null)
	HAS_CURL=$(which curl 2>/dev/null)

	

	# Note: curl has better SOCKS5 proxy support, so prefer it when proxy is configured
	
	# Without proxy, try wget first (if available), then curl as fallback
	if [[ ! -z "$HAS_WGET" ]]; then
		echo "Starting download..."
		TOTAL_SIZE=127631360  # Fallback
		SIZE_MB=$(( TOTAL_SIZE / 1048576 ))
		echo "Estimated file size: ${SIZE_MB} MB"

		wget --tries=10 --timeout=60 --read-timeout=120 --waitretry=5 \
			--quiet --continue --no-check-certificate \
			-O qoder-server.tar.gz "$SERVER_DOWNLOAD_URL" &
		WGET_PID=$!

		# Monitor download progress with percentage
		LAST_PERCENT=-1
		while kill -0 $WGET_PID 2>/dev/null; do
			sleep 1
			if [[ -f qoder-server.tar.gz ]]; then
				CURRENT_SIZE=$(stat -f%z qoder-server.tar.gz 2>/dev/null || stat -c%s qoder-server.tar.gz 2>/dev/null || echo 0)
				if [[ $CURRENT_SIZE -gt 0 && $TOTAL_SIZE -gt 0 ]]; then
					PERCENT=$(( CURRENT_SIZE * 100 / TOTAL_SIZE ))
					if [[ $PERCENT -ne $LAST_PERCENT && $PERCENT -le 100 ]]; then
						echo "Progress: ${PERCENT}%"
						LAST_PERCENT=$PERCENT
					fi
				fi
			fi
		done

		wait $WGET_PID
		DOWNLOAD_SUCCESS=$?
		if [[ $DOWNLOAD_SUCCESS -eq 0 ]]; then
			FINAL_SIZE=$(stat -f%z qoder-server.tar.gz 2>/dev/null || stat -c%s qoder-server.tar.gz 2>/dev/null || echo 0)
			FINAL_MB=$(( FINAL_SIZE / 1048576 ))
			echo "Download completed successfully (${FINAL_MB} MB)"
		fi

		# If wget failed and curl is available, try curl as fallback
		if [[ $DOWNLOAD_SUCCESS -ne 0 && ! -z "$HAS_CURL" ]]; then
			echo ""
			echo "wget failed (exit code: $DOWNLOAD_SUCCESS), trying curl as fallback..."
			rm -f qoder-server.tar.gz

			# Get file size
			TOTAL_SIZE=$(curl -sI --connect-timeout 10 "$SERVER_DOWNLOAD_URL" | grep -i Content-Length | tail -1 | awk '{print $2}' | tr -d '\r')
			if [[ -z "$TOTAL_SIZE" || "$TOTAL_SIZE" -eq 0 ]]; then
				TOTAL_SIZE=127631360
			fi
			SIZE_MB=$(( TOTAL_SIZE / 1048576 ))
			echo "File size: ${SIZE_MB} MB"

			curl --retry 10 --retry-delay 5 --connect-timeout 60 --max-time 600 \
				--location --show-error --silent --insecure \
				--output qoder-server.tar.gz "$SERVER_DOWNLOAD_URL" &
			CURL_PID=$!

			# Monitor download progress with percentage
			LAST_PERCENT=-1
			while kill -0 $CURL_PID 2>/dev/null; do
				sleep 1
				if [[ -f qoder-server.tar.gz ]]; then
					CURRENT_SIZE=$(stat -f%z qoder-server.tar.gz 2>/dev/null || stat -c%s qoder-server.tar.gz 2>/dev/null || echo 0)
					if [[ $CURRENT_SIZE -gt 0 && $TOTAL_SIZE -gt 0 ]]; then
						PERCENT=$(( CURRENT_SIZE * 100 / TOTAL_SIZE ))
						if [[ $PERCENT -ne $LAST_PERCENT && $PERCENT -le 100 ]]; then
							echo "Progress: ${PERCENT}%"
							LAST_PERCENT=$PERCENT
						fi
					fi
				fi
			done

			wait $CURL_PID
			DOWNLOAD_SUCCESS=$?
			if [[ $DOWNLOAD_SUCCESS -eq 0 ]]; then
				echo "Download completed successfully"
			fi
		fi
	elif [[ ! -z "$HAS_CURL" ]]; then
		echo "Starting download..."

		# Get file size
		TOTAL_SIZE=$(curl -sI --connect-timeout 10 "$SERVER_DOWNLOAD_URL" | grep -i Content-Length | tail -1 | awk '{print $2}' | tr -d '\r')
		if [[ -z "$TOTAL_SIZE" || "$TOTAL_SIZE" -eq 0 ]]; then
			TOTAL_SIZE=127631360
		fi
		SIZE_MB=$(( TOTAL_SIZE / 1048576 ))
		echo "File size: ${SIZE_MB} MB"

		curl --retry 10 --retry-delay 5 --connect-timeout 60 --max-time 600 \
			--location --show-error --silent --insecure \
			--output qoder-server.tar.gz "$SERVER_DOWNLOAD_URL" &
		CURL_PID=$!

		# Monitor download progress with percentage
		LAST_PERCENT=-1
		while kill -0 $CURL_PID 2>/dev/null; do
			sleep 1
			if [[ -f qoder-server.tar.gz ]]; then
				CURRENT_SIZE=$(stat -f%z qoder-server.tar.gz 2>/dev/null || stat -c%s qoder-server.tar.gz 2>/dev/null || echo 0)
				if [[ $CURRENT_SIZE -gt 0 && $TOTAL_SIZE -gt 0 ]]; then
					PERCENT=$(( CURRENT_SIZE * 100 / TOTAL_SIZE ))
					if [[ $PERCENT -ne $LAST_PERCENT && $PERCENT -le 100 ]]; then
						echo "Progress: ${PERCENT}%"
						LAST_PERCENT=$PERCENT
					fi
				fi
			fi
		done

		wait $CURL_PID
		DOWNLOAD_SUCCESS=$?
		if [[ $DOWNLOAD_SUCCESS -eq 0 ]]; then
			echo "Download completed successfully"
		fi
	fi
	

	if [[ -z "$HAS_WGET" && -z "$HAS_CURL" ]]; then
		echo "Error: no tool to download server binary (wget or curl required)"
		print_install_results_and_exit 1
	fi

	# Check download result with better error reporting
	if (( $DOWNLOAD_SUCCESS > 0 )); then
		echo "Error: Failed to download server from $SERVER_DOWNLOAD_URL"
		echo "Exit code: $DOWNLOAD_SUCCESS"
		

		# Provide specific error guidance
		case $DOWNLOAD_SUCCESS in
			6|7)
				echo "Hint: Network connectivity issue or cannot connect to server."
				
				;;
			22) echo "Hint: HTTP error occurred. The server might be temporarily unavailable." ;;
			28) echo "Hint: Operation timeout. Try again or check if the server is responding slowly." ;;
			35) echo "Hint: SSL/TLS handshake error. Try using a different network or contact your IT administrator." ;;
			*) echo "Hint: Unknown download error. Please try again or check the download URL." ;;
		esac

		# Check if partial file exists and clean up
		if [[ -f "qoder-server.tar.gz" ]]; then
			echo "Cleaning up partial download..."
			rm -f qoder-server.tar.gz
		fi

		

		print_install_results_and_exit 1
	fi

	# Verify download file exists and has content
	if [[ ! -f "qoder-server.tar.gz" ]]; then
		echo "Error: Download completed but file qoder-server.tar.gz not found"
		print_install_results_and_exit 1
	fi

	# Check if downloaded file is not empty
	if [[ ! -s "qoder-server.tar.gz" ]]; then
		echo "Error: Downloaded file is empty"
		rm -f qoder-server.tar.gz
		print_install_results_and_exit 1
	fi

	echo "Download completed successfully. File size: $(ls -lh qoder-server.tar.gz | awk '{print $5}')"

	# Basic integrity check - verify it's a valid gzip file
	if ! gzip -t qoder-server.tar.gz 2>/dev/null; then
		echo "Error: Downloaded file appears to be corrupted (not a valid gzip file)"
		echo "This might indicate a network issue or server problem. Please try again."
		rm -f qoder-server.tar.gz
		print_install_results_and_exit 1
	fi

	echo "File integrity check passed. Extracting..."
	tar -xf qoder-server.tar.gz --strip-components 1
	if (( $? > 0 )); then
		echo "Error while extracting server contents"
		print_install_results_and_exit 1
	fi

	# Get old commit from product.json and replace with new commit
	if [[ -f "$SERVER_DIR/product.json" ]]; then
		OLD_COMMIT=$(grep -o '"commit": *"[^"]*"' "$SERVER_DIR/product.json" | cut -d'"' -f4)
		if [[ ! -z "$OLD_COMMIT" ]]; then
			find "$SERVER_DIR" -type f -exec perl -pi -e "s/$OLD_COMMIT/$DISTRO_COMMIT/g" {} +
		fi
	fi

	# Rename all files containing 'codium' in bin directory
	find "$SERVER_DIR/bin" -type f -name "*codium*" | while read file; do
		new_name=$(echo "$file" | sed 's/codium/qoder/g')
		mv "$file" "$new_name"
	done

	if [[ ! -f $SERVER_SCRIPT ]]; then
		echo "Error server contents are corrupted"
		print_install_results_and_exit 1
	fi

	rm -f qoder-server.tar.gz

	popd > /dev/null
else
	echo "Server script already installed in $SERVER_SCRIPT"
fi

# Try to find if server is already running
if [[ -f $SERVER_PIDFILE ]]; then
	SERVER_PID="$(cat $SERVER_PIDFILE)"
	SERVER_RUNNING_PROCESS="$(ps -o pid,args -p $SERVER_PID | grep $SERVER_SCRIPT)"
	if [[ -n $SERVER_RUNNING_PROCESS ]]; then
		# Extract PID from process info
		SERVER_ID="$SERVER_PID"
	fi
else
	SERVER_RUNNING_PROCESS="$(ps -o pid,args -A | grep $SERVER_SCRIPT | grep -v grep)"
	if [[ -n $SERVER_RUNNING_PROCESS ]]; then
		# Extract PID from process list (first column)
		SERVER_ID="$(echo $SERVER_RUNNING_PROCESS | awk '{print $1}')"
	fi
fi

if [[ -z $SERVER_RUNNING_PROCESS ]]; then
	if [[ -f $SERVER_LOGFILE ]]; then
		rm $SERVER_LOGFILE
	fi
	if [[ -f $SERVER_TOKENFILE ]]; then
		rm $SERVER_TOKENFILE
	fi

	touch $SERVER_TOKENFILE
	chmod 600 $SERVER_TOKENFILE
	SERVER_CONNECTION_TOKEN="9db1d7xxxxxxxx"
	echo $SERVER_CONNECTION_TOKEN > $SERVER_TOKENFILE

	$SERVER_SCRIPT --start-server --host=127.0.0.1 $SERVER_LISTEN_FLAG $SERVER_INITIAL_EXTENSIONS --connection-token-file $SERVER_TOKENFILE --telemetry-level off --enable-remote-auto-shutdown --accept-server-license-terms &> $SERVER_LOGFILE &
	SERVER_ID=$!
	echo $SERVER_ID > $SERVER_PIDFILE
else
	echo "Server script is already running $SERVER_SCRIPT"
fi

if [[ -f $SERVER_TOKENFILE ]]; then
	SERVER_CONNECTION_TOKEN="$(cat $SERVER_TOKENFILE)"
else
	echo "Error server token file not found $SERVER_TOKENFILE"
	print_install_results_and_exit 1
fi

if [[ -f $SERVER_LOGFILE ]]; then
	# Record start time for server startup monitoring
	START_TIME=$SECONDS

	for i in {1..120}; do
		LISTENING_ON="$(cat $SERVER_LOGFILE | grep -E 'Extension host agent listening on .+' | sed 's/Extension host agent listening on //')"
		if [[ -n $LISTENING_ON ]]; then
			break
		fi
		sleep 0.5
	done

	# Calculate and log server startup time
	SERVER_STARTUP_TIME=$((SECONDS - START_TIME))
	echo "Server startup time: $SERVER_STARTUP_TIME""s"

	if [[ -z $LISTENING_ON ]]; then
		echo "Error server did not start successfully"
		print_install_results_and_exit 1
	fi

	# Warn if server startup took longer than 10 seconds
	if [[ $SERVER_STARTUP_TIME -gt 10 ]]; then
		echo "Warning: Server startup took $SERVER_STARTUP_TIME""s (>10s). The remote machine may have limited performance."
	fi
else
	echo "Error server log file not found $SERVER_LOGFILE"
	print_install_results_and_exit 1
fi

# Finish server setup
print_install_results_and_exit 0

# Keep SSH session alive by monitoring server process
# This prevents SSH disconnection and avoids repeated authentication
if [[ -n $SERVER_ID ]]; then
	while true; do
		if ! ps -p $SERVER_ID > /dev/null 2>&1; then
			echo "Server process died, exiting"
			exit 0
		fi
		sleep 30
	done
fi

[Info  - 04:36:04.255] Installing SSH server...
[Info  - 04:36:04.791] Starting download...
[Info  - 04:36:04.791] Estimated file size: 121 MB
[Info  - 04:39:02.291] wget failed (exit code: 4), trying curl as fallback...
[Info  - 04:39:12.396] File size: 121 MB
[Info  - 04:39:26.522] curl: (28) Failed to connect to download.qoder.com port 443 after 14200 ms: Couldn't connect to server
[Info  - 04:39:46.35] r
[Info  - 04:39:46.35] curl: (28) Failed to connect to download.qoder.com port 443 after 14198 ms: Couldn't connect to server
[Info  - 04:40:04.944] r
[Info  - 04:40:04.944] curl: (28) Failed to connect to download.qoder.com port 443 after 14196 ms: Couldn't connect to se
[Info  - 04:40:04.944] server
[Info  - 04:40:24.150] r
[Info  - 04:40:24.150] curl: (28) Failed to connect to download.qoder.com port 443 after 14199 ms: Couldn't connec
[Info  - 04:40:24.150] ect to server
[Info  - 04:40:43.404] r
[Info  - 04:40:43.404] curl: (28) Failed to connect to download.qoder.com port 443 after 14195 ms: Couldn't connect to server
[Info  - 04:41:02.550] r
[Info  - 04:41:02.550] curl: (28) Failed to connect to download.qoder.com port 443 after 14195 ms: Couldn't connect to server
[Info  - 04:41:21.807] r
[Info  - 04:41:21.807] curl: (28) Failed to connect to download.qoder.com port 443 after 14195 ms: Couldn't con
[Info  - 04:41:21.808] onnect to server
[Info  - 04:41:40.948] r
[Info  - 04:41:40.948] curl: (28) Failed to connect to download.qoder.com port 443 after 14196 ms: Couldn't connect to server
[Info  - 04:42:00.313] r
[Info  - 04:42:00.313] curl: (28) Failed to connect to download.qoder.com port 443 after 14260 ms: Couldn't connect to server
[Info  - 04:42:19.456] r
[Info  - 04:42:19.456] curl: (28) Failed to connect to download.qoder.com port 443 after 14195 ms: Couldn't connect to server
[Info  - 04:42:38.595] r
[Info  - 04:42:38.595] curl: (28) Failed to connect to download.qoder.com port 443 after 14195 ms: Couldn't connect to server
[Info  - 04:42:39.518] l: (28) Failed to connect to download.qoder.com port 443 after 14195 ms: Couldn't connect to server
[Info  - 04:42:39.518] curl: (28) Failed to connect to download.qoder.com port 443 after 14196 ms: Couldn't connect to server
[Info  - 04:42:39.518] curl: (28) Failed to connect to download.qoder.com port 443 after 14260 ms: Couldn't connect to server
[Info  - 04:42:39.518] curl: (28) Failed to connect to download.qoder.com port 443 after 14195 ms: Couldn't connect to server
[Info  - 04:42:39.518] curl: (28) Failed to connect to download.qoder.com port 443 after 14195 ms: Couldn't connect to server
[Error  - 04:42:39.521] Server install script failed with exit code: 1
[Error  - 04:42:39.521] Script stdout:
Starting download...
Estimated file size: 121 MB

wget failed (exit code: 4), trying curl as fallback...
File size: 121 MB
Error: Failed to download server from https://download.qoder.com/server/0.2.20/qoder-reh-linux-x64-0.2.20.tar.gz
Exit code: 28
Hint: Operation timeout. Try again or check if the server is responding slowly.
e2aee38d375864cbd24bb5f8: start
exitCode==1==
listeningOn====
connectionToken====
logFile==/data/user/xxxxxxxxx/.qoder-server/.41eefc9d6820b32df7892471dc26c255bf238b67.log==
osReleaseId==rocky==
arch==x86_64==
platform==linux==
tmpDir==/run/user/xxxxxx==
serverStartupTime====
e2aee38d375864cbd24bb5f8: end

[Error  - 04:42:39.521] Script stderr:
curl: (28) Failed to connect to download.qoder.com port 443 after 14200 ms: Couldn't connect to server
curl: (28) Failed to connect to download.qoder.com port 443 after 14198 ms: Couldn't connect to server
curl: (28) Failed to connect to download.qoder.com port 443 after 14196 ms: Couldn't connect to server
curl: (28) Failed to connect to download.qoder.com port 443 after 14199 ms: Couldn't connect to server
curl: (28) Failed to connect to download.qoder.com port 443 after 14195 ms: Couldn't connect to server
curl: (28) Failed to connect to download.qoder.com port 443 after 14195 ms: Couldn't connect to server
curl: (28) Failed to connect to download.qoder.com port 443 after 14195 ms: Couldn't connect to server
curl: (28) Failed to connect to download.qoder.com port 443 after 14196 ms: Couldn't connect to server
curl: (28) Failed to connect to download.qoder.com port 443 after 14260 ms: Couldn't connect to server
curl: (28) Failed to connect to download.qoder.com port 443 after 14195 ms: Couldn't connect to server
curl: (28) Failed to connect to download.qoder.com port 443 after 14195 ms: Couldn't connect to server

[Error  - 04:42:39.522] Error resolving authority
Error: Couldn't install Qoder server on remote server (exit code: 1). See output log for details.
	at e.installCodeServer (/Applications/Qoder.app/Contents/Resources/app/extensions/open-remote-ssh/dist/extension.js:1:469757)
	at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
	at async /Applications/Qoder.app/Contents/Resources/app/extensions/open-remote-ssh/dist/extension.js:1:401727

由于这是一台隔离主机,所以出现curl和wget无法请求到资源是符合预期的,但期望官方能增加VSCode或cursor那样在本地下载后再传输到主机上解包的功能。

感谢反馈,我们已经确认,您这个场景的确有点问题,目前我们已经提单排需求中 :grinning_face:

1 Like

我也有类似的求助,我在与外网隔离的局域网环境里,想把Qoder的内置智能会话功能改为调用局域网内安装的AI大模型(有多个可选),如何修改MCP配置文件(如果通过Qoder设置-MCP菜单就可以实现这一愿望的话)?

你意思是想调用你本地的大模型?这个应该暂时是不能实现的

1 Like

它还给我提供了另一种思路如下,您觉得可行吗,感谢您的关注:

:magnifying_glass_tilted_left: 精准分析:Qoder v0.2.20 是否能连接局域网 Ollama?

直接结论:

:cross_mark: 不能通过图形界面直接设置为 Ollama 模型
:warning: 尚无内置 MCP 服务或“自定义 LLM API”配置项
:white_check_mark: 但可通过“反向代理 + 协议转换”方式实现间接对接(技术可行)

这是当前阶段唯一可靠的方法。


:puzzle_piece: 方案:使用「本地代理服务器」伪装成 Qoder 的云端模型

我们不修改 Qoder 本身,而是让它以为自己在调用官方云服务,实际上请求被拦截并转发到你企业内网的 Ollama 服务

[Qoder v0.2.20] 
   → 发起请求到 https://api.qoder.ai/v1/chat/completions
        → 被本地代理拦截(如 mitmproxy / Flask)
             → 转换协议,调用 http://192.168.10.50:11434/api/generate
                  ← 返回 Ollama 生成结果
                       ← 再伪装成 Qoder 官方格式返回给客户端

:bullseye: 效果:Qoder 完全无感,却用的是你们企业私有模型!


:white_check_mark: 实现步骤(完整可操作)

第一步:确保 Ollama 可访问

假设你的 Ollama 运行在局域网某台机器上:

IP: 192.168.10.50
Port: 11434
Model: llama3 或 qwen:7b

从运行 Qoder 的电脑测试连通性:

curl http://192.168.10.50:11434/api/tags

应返回模型列表 :+1:


第二步:搭建本地代理服务器(Python 实现)

1. 准备环境

安装 Python(推荐 Python 3.10+ x64

打开命令行安装依赖:

pip install flask requests

2. 创建代理脚本 qoder-proxy.py

python

# qoder-proxy.py

from flask import Flask, request, jsonify

import requests

import json

app = Flask(_name_)

# ================== 配置区 ==================

OLLAMA_BASE_URL = “http://192.168.10.50:11434” # 修改为你的 Ollama 地址

OLLAMA_MODEL = “llama3” # 修改为你使用的模型名

PROXY_HOST = “127.0.0.1”

PROXY_PORT = 443 # 模拟 HTTPS

USE_HTTPS = False # 先用 HTTP 测试,成功后再上 HTTPS

# ============================================

@app.route(‘/v1/chat/completions’, methods=[‘POST’])

def chat_completions():

try:

data = request.json

messages = data.get(“messages”, )

# 构造 prompt(简单拼接)

prompt = “”

for msg in messages:

role = msg[“role”].strip(“:”)

content = msg[“content”]

prompt += f"{role.capitalize()}: {content}\n"

prompt += "Assistant: "

# 调用 Ollama API

ollama_response = requests.post(

f"{OLLAMA_BASE_URL}/api/generate",

json={

“model”: OLLAMA_MODEL,

“prompt”: prompt,

“stream”: False

},

timeout=60

)

if ollama_response.status_code != 200:

return “Ollama Error”, 502

resp_data = ollama_response.json()

text = resp_data.get(“response”, “”).strip()

# 构造符合 Qoder 预期的响应格式(模拟 OpenAI 格式)

return jsonify({

“id”: “chat-”+str(hash(text))[:8],

“object”: “chat.completion”,

“created”: 1717171717,

“model”: OLLAMA_MODEL,

“choices”: [

{

“index”: 0,

“message”: {

“role”: “assistant”,

“content”: text

},

“finish_reason”: “stop”

}

],

“usage”: {

“prompt_tokens”: len(prompt.split()),

“completion_tokens”: len(text.split()),

“total_tokens”: len(prompt.split()) + len(text.split())

}

})

except Exception as e:

print(f"[ERROR] {e}")

return “Internal Server Error”, 500

if _name_ == ‘_main_’:

print(f":white_check_mark: Qoder-Ollama 代理服务器启动中…")

print(f":globe_with_meridians: 访问地址: http://{PROXY_HOST}:{PROXY_PORT}/v1/chat/completions")

print(f":wrench: 将 Qoder 请求指向此代理(需配合 hosts 修改)")

app.run(host=PROXY_HOST, port=PROXY_PORT, ssl_context=‘adhoc’ if USE_HTTPS else None)


第三步:修改系统 hosts 文件(欺骗 DNS)

以管理员身份编辑:

C:\Windows\System32\drivers\etc\hosts

添加一行:

127.1.0.1 api.qoder.ai

:light_bulb: 注意:这里写 127.1.0.1 而不是 127.0.0.1 是为了避免与其他本地服务冲突,也可以用其他保留 IP 如 0.0.0.0

然后我们在本地监听这个 IP:

修改脚本中的 PROXY_HOST = "127.1.0.1"


第四步:运行代理服务

python qoder-proxy.py

你会看到输出:

✅ Qoder-Ollama 代理服务器启动中...
🌐 访问地址: http://127.1.0.1:443/v1/chat/completions

此时你的电脑就在 127.1.0.1:443 上监听了!


第五步:启动 Qoder 并测试

  1. 正常打开 Qoder v0.2.20

  2. 在聊天窗口输入问题,例如:“你好,你是谁?”

  3. 观察代理终端是否有日志打印

  4. 如果收到回复,并且内容来自你部署的模型(比如 llama3 的语气),就成功了!


:locked: 进阶建议(提升稳定性)

功能 方法
支持 HTTPS 使用 ssl_context=('cert.pem', 'key.pem') 生成证书
多模型切换 在 headers 中加 X-Model: qwen:7b 动态选择
日志记录 把请求/响应保存到文件用于调试
批量部署 打包成 .exe 分发给团队成员

:package: 我可以为你做什么?

如果你希望我帮你:

  1. :white_check_mark: 生成完整的 .py 文件包(带注释)

  2. :white_check_mark: 提供一键启动 .bat 脚本

  3. :white_check_mark: 编译成 Windows .exe(无需安装 Python)

  4. :white_check_mark: 支持自动后台运行(隐藏窗口)

  5. :white_check_mark: 添加简易配置界面(JSON 设置)

:backhand_index_pointing_right: 只需告诉我:

  • 你的 Ollama 服务器 IP 和端口

  • 使用的模型名称(如 qwen:7b

  • 是否需要 HTTPS 支持

  • 是否希望打包成 .exe

我可以立即为你制作一个 开箱即用的企业内部署版本 :blush:


:bullseye: 总结:你现在该怎么办?

步骤 操作
1 确认 Ollama 可访问(curl /api/tags
2 安装 Python,运行我提供的代理脚本
3 修改 hosts,将 api.qoder.ai 指向本地代理
4 启动代理 + 启动 Qoder,测试对话
5 成功后联系 Qoder 官方升级到 v0.6+,获得原生支持

:bell: 强烈建议下一步:联系 Qoder 团队获取新版

你可以这样问他们:

“我们是企业用户,希望将 Qoder 对接内部 Ollama 模型,请问 v0.2.20 是否支持?如果不支持,最新版是否提供 MCP 或自定义 API 接入能力?能否提供 Windows 最新版安装包?”

一旦升级到 v0.6+,就可以直接在设置里填:

模型提供者:Custom (HTTP)
API Base URL: http://192.168.10.50:11434
Model Name: llama3

再也不用折腾代理了!

我:想在Qoder的设置里把它的智能会话后台调用的云端大语言模型修改为企业局域网里安装的大语言模型,而且是通过Ollama模型基座服务调取,应该从菜单“Qoder设置-mcp服务“项进去吧,怎么设置呢?

千问plus:

你好!你提到的 Qoder 是一款由国内团队开发的 AI 编程助手工具,支持智能补全、代码生成、对话问答等功能。目前 Qoder 确实支持通过 MCP(Model Calling Protocol)服务来配置后端大语言模型(LLM)的调用方式,允许用户将默认的云端模型切换为本地或企业内网部署的模型服务。

你希望将 Qoder 的智能会话后端从云端模型切换到 企业局域网中通过 Ollama 部署的大语言模型,这个目标是完全可行的。以下是详细的设置步骤和建议:


:white_check_mark: 一、确认前提条件

  1. Ollama 已在企业局域网服务器上部署并运行

    • 例如:http://192.168.10.50:11434

    • 可通过浏览器或 curl http://192.168.10.50:11434/api/tags 测试是否返回模型列表

  2. 目标模型已加载到 Ollama 中

    • 比如:llama3, qwen:7b, deepseek-coder
  3. 你的客户端(运行 Qoder 的电脑)可以访问该 Ollama 服务地址


:white_check_mark: 二、进入 Qoder 设置界面

  1. 打开 Qoder(VS Code 插件或其他 IDE 版本)

  2. 进入设置菜单:

    • VS Code:点击左侧 AI 图标 → 点击右上角「齿轮」→ 选择 “Qoder Settings”

    • 或通过命令面板输入:> Qoder: Open Settings


:white_check_mark: 三、配置 MCP 服务连接 Ollama

目前 Qoder 支持通过 MCP Server 的方式接入自定义 LLM 后端,你需要配置一个能转发请求到 Ollama 的 MCP 服务。

方法一:使用内置的 Ollama 支持(推荐,如果已支持)

部分新版 Qoder 已原生支持 Ollama 协议直连,请检查是否有如下选项:

  • Qoder SettingsModel Provider 中选择:

    • Custom (Ollama)
  • 填写:

    • API Base URL: http://<your-ollama-ip>:11434

      • 示例:http://192.168.10.50:11434
    • Model Name: 如 llama3qwen:7b

    • (可选)Additional Headers: 留空或根据需要添加认证头

:white_check_mark: 保存后重启 Qoder 或刷新会话即可生效。


方法二:通过自建 MCP Server 转接(通用方案)

如果 Qoder 当前不支持直接连接 Ollama,则需通过 MCP Server 中转。

步骤 1:启动一个支持 Ollama 的 MCP Server

你可以使用开源的 MCP Server 实现,比如基于 Node.js 的 mcp-spec/implementation-js 或 Python 实现。

示例项目(Python):

git clone https://github.com/ouyanghuiyu/ollama-mcp-server.git
cd ollama-mcp-server
pip install -r requirements.txt

修改配置文件中的:

python

1

2

3

OLLAMA_BASE_URL = “http://192.168.10.50:11434

DEFAULT_MODEL = “llama3”

启动服务:

uvicorn server:app --host 0.0.0.0 --port 8080

此时你的 MCP 服务地址为:http://localhost:8080http://192.168.10.100:8080

步骤 2:在 Qoder 中配置 MCP 服务
  • 回到 Qoder 设置页面

  • 找到菜单项:“Qoder 设置” → “MCP 服务”

  • 添加新的 MCP 服务:

    • 名称:My Ollama MCP

    • 地址:http://192.168.10.100:8080 ← 改为你实际运行 MCP Server 的 IP 和端口

  • 保存并启用该 MCP 服务

步骤 3:选择 MCP 提供的模型进行对话
  • 在聊天窗口中,选择模型来源时应能看到来自 MCP 的模型(如 llama3@local

  • 切换过去即可使用企业内网模型


:white_check_mark: 四、验证是否成功

  1. 发起一次提问,如:“你是谁?”

  2. 观察响应时间与内容风格是否符合你部署的模型(比如 llama3 的语气)

  3. 查看 Ollama 服务器日志是否有 /api/generate 请求记录


:locked_with_key: 安全建议(企业环境)

  • 使用防火墙限制 11434 端口仅允许内部可信 IP 访问

  • 若需身份验证,可在 MCP Server 层增加 Token 验证机制

  • 可考虑使用反向代理(Nginx + Basic Auth / JWT)保护 Ollama 接口


:puzzle_piece: 补充说明

功能 是否支持
直连 Ollama API :warning: 视版本而定(v1.4+ 可能支持)
通过 MCP 中转 :white_check_mark: 完全支持
多模型切换 :white_check_mark: 支持(通过 MCP 返回多个 model)
流式输出 :white_check_mark: 支持(需 MCP 实现 streaming)

:red_question_mark:如果你不确定当前 Qoder 是否支持直连 Ollama

请告诉我你使用的 Qoder 版本号运行环境(VS Code / JetBrains / 其他),我可以帮你查证是否可以直接填写 Ollama 地址。


:bullseye: 总结操作路径:

Qoder 设置 → MCP 服务 → 添加 MCP Server 地址(指向你的中转服务)→ 启用 → 选择对应模型开始对话

只要你的 MCP Server 正确地把请求转发给 Ollama,就能实现完全本地化、私有化的 AI 对话体验!

如有具体错误信息或截图,也欢迎贴出来,我进一步帮你排查

我因为在于外网隔离的环境,只能使用安装在企业局域网里的大模型,所以想把Qoder的智能会话的后台调用大模型从云端的改为内网的,我刚问了阿里云百炼上的千问plus,说是可以。您这是qoder的开发者吧,在这个问题上应该更权威,又看到网上说明年qoder会开放用户自己选择后台大模型用内网还是外网,是这样吗?

Hello Austin followup一下当前issue是否已经被解决了呀~

已经解决了,可以在新版本体验尝试下 有问题可以反馈