智能助手网
标签聚合 SSH

/tag/SSH

linux.do · 2026-04-18 17:07:58+08:00 · tech

起因 今天在使用WSL上的Centos时, 发现Vscode远程连接上不上了, 然后想起来vscode之前下载claude插件自动更新了一次, 导致它的版本变成了v1.106.0, 然后就连不上了. 从 VS Code 1.99(2025年3月发布) 开始,官方预编译的 VS Code Server 对 Linux 发行版的系统依赖做了升级,要求远端服务器必须满足: glibc >= 2.28 libstdc++ >= 3.4.25 以及对应的动态链接环境 ssh远程连接时错误信息如下: [2026-04-18 03:33:45.663] Starting server: /home/user/.vscode-server/bin/ac4cbdf48759c7d8c3eb91ffe6bb04316e263c57/bin/code-server --host=127.0.0.1 --port=0 --connection-token=1869292326-2464295744-3154885190-4149685785 --use-host-proxy --without-browser-env-var --disable-websocket-compression --accept-server-license-terms --telemetry-level=all [2026-04-18 03:33:45.664] /home/user/.vscode-server/bin/ac4cbdf48759c7d8c3eb91ffe6bb04316e263c57/node: /lib64/libm.so.6: version `GLIBC_2.27' not found (required by /home/user/.vscode-server/bin/ac4cbdf48759c7d8c3eb91ffe6bb04316e263c57/node) [2026-04-18 03:33:45.664] /home/user/.vscode-server/bin/ac4cbdf48759c7d8c3eb91ffe6bb04316e263c57/node: /lib64/libc.so.6: version `GLIBC_2.27' not found (required by /home/user/.vscode-server/bin/ac4cbdf48759c7d8c3eb91ffe6bb04316e263c57/node) [2026-04-18 03:33:45.664] /home/user/.vscode-server/bin/ac4cbdf48759c7d8c3eb91ffe6bb04316e263c57/node: /lib64/libc.so.6: version `GLIBC_2.28' not found (required by /home/user/.vscode-server/bin/ac4cbdf48759c7d8c3eb91ffe6bb04316e263c57/node) [2026-04-18 03:33:45.664] /home/user/.vscode-server/bin/ac4cbdf48759c7d8c3eb91ffe6bb04316e263c57/node: /lib64/libc.so.6: version `GLIBC_2.25' not found (required by /home/user/.vscode-server/bin/ac4cbdf48759c7d8c3eb91ffe6bb04316e263c57/node) 解决方法一: 官方推荐的自定义运行库法 code.visualstudio.com Can I run VS Code Server on older Linux distributions? - Remote Development FAQ This article covers frequently asked questions for each of the Visual Studio Code Remote Development extensions. See the SSH, Containers, and WSL articles for more details on setting up and working with each of their respective capabilities. Or try... 以下是AI关于这个文档的解释: VS Code 1.99 之后,官方发的 VS Code Server 二进制 默认要求远端系统有较新的: glibc >= 2.28 libstdc++ >= 3.4.25 以及相应动态链接环境 如果你的老服务器系统本身太旧,比如 CentOS 7,没有这些库,Server 本体会启动失败 这时候可以额外准备一套“较新的运行库目录”,让 VS Code Server 不要使用系统自带旧 glibc,而是改为加载你提供的那一套新库. 这个“较新的运行库目录”,官方建议你用 Crosstool-ng 去构建,这就是文档里说的 sysroot 准备工具: v0.18.x 以上的 patchelf. Release 0.18.0 · NixOS/patchelf · GitHub 在 rpmfind.net 中找到所需要的 glibc 2.28 地址: RPM resource glibc 选择AlmaLinux 8.10 BaseOS for aarch64 glibc-2.28-251.el8_10.31.aarch64.rpm 在 rpmfind.net 中找到所需要的 libstdc++ 地址: https://www.rpmfind.net/linux/almalinux/8.10/BaseOS/x86_64/os/Packages/libstdc++-8.5.0-28.el8_10.alma.1.i686.rpm 选择AlmaLinux 8.10 BaseOS for aarch64 libstdc++-8.5.0-28.el8_10.alma.1.i686.rpm 不要直接安装 rpm!!! mkdir -p /home/user/lib/vscode_server_linux_root mv glibc-2.28-251.el8.x86_64.rpm /home/user/lib/vscode_server_linux_root/ mv libstdc++-8.5.0-21.el8.x86_64.rpm /home/user/lib/vscode_server_linux_root/ cd /home/user/lib/vscode_server_linux_root # 将rpm文件解压到当前目录 rpm2cpio glibc-2.28-251.el8.x86_64.rpm | cpio -idmv rpm2cpio libstdc++-8.5.0-21.el8.x86_64.rpm | cpio -idmv # 检查下 so 文件中的 ABI 兼容版本是否符合 VSCode 或者 Node.js 的要求 strings ./usr/lib/libc.so.6 | grep -E '^GLIBC_[0-9.]+' | sort strings ./usr/lib/libstdc++.so.6 | grep -E '^GLIBCXX_[0-9.]+' | sort # 设置环境变量, 两个环境变量都试一下 export VSCODE_SERVER_CUSTOM_GLIBC_LINKER=/home/user/my_lib/vscode_server_linux_root/usr/lib # export VSCODE_SERVER_CUSTOM_GLIBC_LINKER=/home/flipped/my_lib/vscode_server_linux_root/usr/lib/ld-linux.so.2 export VSCODE_SERVER_CUSTOM_GLIBC_PATH=/home/user/my_lib/vscode_server_linux_root/usr/lib export VSCODE_SERVER_PATCHELF_PATH=/home/user/my_lib/vscode_server_linux_root/bin 理论上这样之后, node 应该可以正常启动了, 但是我在wsl上的centos7.9进行测试时, 即使设置了环境变量, 服务器上的node也没有到我指定的目录下去找2.28的glibc. 然后我也尝试了手动patch node. 但发现patch后, node --version 都运行不了. [!NOTE] 不一定起作用 若环境变量不生效,可尝试直接修改 VS Code Server 内嵌 Node.js 的动态链接路径: patchelf --set-interpreter ${CUSTOM_LIB_DIR}/usr/lib64/ld-linux-x86-64.so.2 \ --set-rpath ${CUSTOM_LIB_DIR}/usr/lib64 \ ~/.vscode-server/bin/<VSCode版本号>/node 解决方法2: 第三方补丁工具 从社区仓库下载与本地 VS Code 版本匹配的包: 仓库地址: MikeWang000000/vscode-server-centos7 查看本地 VS Code 版本:点击左下角齿轮 → 关于 → 复制版本哈希(如 ac4cbdf48759c7d8c3eb91ffe6bb04316e263c57 ) # 1. 创建VS Code Server目录(若已存在则跳过) mkdir -p ~/.vscode-server # 2. 解压下载的预补丁包 tar xzf vscode-server_*.tar.gz -C ~/.vscode-server --strip-components 1 # 3. 执行补丁脚本 ~/.vscode-server/code-latest --patch-now # 4. 替换官方内嵌的Node.js(替换为预编译的兼容版本) cp -f ~/.vscode-server/cli/servers/Stable-<版本哈希>/server/node \ ~/.vscode-server/bin/<版本哈希>/node 使用这个方案, 我电脑上能够连接到centos了, 但是远程连接后, 无法在vscode的集成终端中使用 code a.log 打开服务器上的文件, 报错如下: user@user:test$ code . Unable to connect to VS Code server: Error in request. Error: connect ENOENT /run/user/1000/vscode-ipc-d2af735a-73ee-499c-9f8c-48fa19a6199e.sock at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1637:16) { errno: -2, code: 'ENOENT', syscall: 'connect', address: '/run/user/1000/vscode-ipc-d2af735a-73ee-499c-9f8c-48fa19a6199e.sock' } 除了上面给出的预编译后的node文件, 在下面这个issue里面针对centos上运行v18以后的nodejs的问题, 也提供了一个预编译好的版本 github.com/nodejs/node Node.js is showing error "node: /lib64/libm.so.6: version `GLIBC_2.27' not found (required by node)" 已打开 05:40AM - 28 Mar 24 UTC 已关闭 08:38PM - 17 May 25 UTC yogeshlc ### Version node-v20.11.0-linux-x64.tar.xz ### Platform Linux yogVM 5.4.17-21 … 36.325.5.1.el7uek.x86_64 ### Subsystem _No response_ ### What steps will reproduce the bug? - Extract node.js tar at location /usr/local - check node --version cmd which is failing with error Error: node --version node: /lib64/libm.so.6: version `GLIBC_2.27' not found (required by node) node: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.20' not found (required by node) node: /lib64/libstdc++.so.6: version `CXXABI_1.3.9' not found (required by node) node: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found (required by node) node: /lib64/libc.so.6: version `GLIBC_2.28' not found (required by node) node: /lib64/libc.so.6: version `GLIBC_2.25' not found (required by node) ### How often does it reproduce? Is there a required condition? _No response_ ### What is the expected behavior? Why is that the expected behavior? node --version v20.11.0 ### What do you see instead? node --version node: /lib64/libm.so.6: version `GLIBC_2.27' not found (required by node) node: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.20' not found (required by node) node: /lib64/libstdc++.so.6: version `CXXABI_1.3.9' not found (required by node) node: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found (required by node) node: /lib64/libc.so.6: version `GLIBC_2.28' not found (required by node) node: /lib64/libc.so.6: version `GLIBC_2.25' not found (required by node) ### Additional information _No response_ 但我用这个, 虽然也能正常连接, 但无法打开vscode中的集成终端. 报错如下. 结尾 佬友们帮忙看看, 为什么我使用官方文档里面的方法, 设置环境变量之后还是没有办法让服务器上的node到我指定的目录下去找glibc. 2 个帖子 - 2 位参与者 阅读完整话题

hnrss.org · 2026-04-17 18:00:39+08:00 · tech

I've made [candalf]( https://github.com/jarmo/candalf ) to setup my own PC and servers by using simple shell scripts instead of using something more complex like Ansible or similar. It's been used by me to manage production servers (physical and VM-s) running Linux/FreeBSD for years now and I'm also using it to configure my own laptop (Linux) so that if I install OS from scratch I can get up to speed really quickly and to also have everything "documented" in the scripts to not waste extra time in the future. I recently improved it a little to also support macOS, but have not used it for production usage having that OS. Maybe someone else also finds it useful. If not then also no harm is done. Comments URL: https://news.ycombinator.com/item?id=47804236 Points: 2 # Comments: 0

linux.do · 2026-04-17 03:14:00+08:00 · tech

https://openai.com/index/codex-for-almost-everything/ 快速试用了一下,Computer Use 的主要卖点是可以在后台操作,不会占据前台空间。 在操作的时候,会有一个虚拟光标显示出来: 定时 Automation 是另一个比较有趣的功能,主要的特殊之处在于:它可以直接在已有的 Session 上继续,类似于每一段时间发送一条消息触发,保留全程的 Context. 那么以后做自动化的流程会类似于: 在 Codex App 中新开一个 Session,先对话操作一次。 让 Codex 设置一下自动化。 每次自动化运行都直接在已有的 Session 中继续。 不过这个做法应该也相当消耗 Token. Memory 系统实验性启动,在设置中打开即可: in-app Browser 还没试用,但看演示可以直接在浏览器中选元素,对前端开发、样式描述等很方便。 Remote SSH 是我心心念念了很久的功能,不过还没找到设置,似乎在 alpha stage,等会更新使用体验。 Remote SSH 需要在本地的 ~/.codex/config.toml 中添加 [features] remote_connections = true 远程机器的 default shell 需要设置为 bash,否则会报错。 1 个帖子 - 1 位参与者 阅读完整话题

linux.do · 2026-04-16 14:51:49+08:00 · tech

最近在使用 Trae IDE 进行开发,有一说一,界面的确清爽好用。但因为我个人的工作流是 Windows Trae 客户端 + SSH 远程连接 Linux 服务器 ,并且配置 第三方的 OpenAI 兼容接口 ,当使用工具内自带的openai接口功能接入gpt-5.4后,发现 AI 开始输出代码或文本时, 前端界面的字符会排版严重错乱,导致修改的代码文件出错。 通过抓包脚本进行调试,发现跟返回的流式传输有关,于是写了一个 Go 语言“智能防抖代理”来解决,终于客户端回复正常了。 脚本功能如下: 物理节流 :接受上游返回后,强制将输出速度限制在 128字节 / 10ms 。增加数据返回客户端的延迟,契合 Trae 的前端渲染节奏, 根治排版混乱 。 UTF-8 边界识别 :在切分数据包时,自动通过二进制位运算检测切口。如果发现切在半个汉字上,自动回退字节, 保证发给前端的每一个字符都是完整的,防止 4054 解析报错 。 接口强转与劫持补全 :Trae自带的GPT-5.4选项实际请求模型是gpt-5.4-2026-03-05,为了方便使用,对 /v1/models 接口请求时强制注入 gpt-5.4-2026-03-05 模型,并增加对话模型替换功能,将前端请求的模型替换成后端支持的模型,例如:gpt-5.4,哪怕你的上游接口没有这个模型也能秒过校验并中转为上游支持的模型。 脚本使用方法 脚本需要运行在 被 SSH 连接的 Linux 远程服务器 上。 1. 保存代码并编译 在 Linux 服务器上创建一个文件 main.go ,将文末的代码粘贴进去。然后执行编译: # 确保你的服务器安装了 Go 环境 go build -o trae_proxy main.go 2. 运行代理服务 必须使用 sudo 运行 ,因为脚本会自动修改 /etc/hosts 劫持域名并生成安装本地根证书。 sudo ./trae_proxy -target "http://你的第三方API地址" -model "你要强制使用的模型" -debug 参数说明: -target :你的第三方 API 基础地址(不需要加 /v1/chat/completions )。 -model :(可选)无视你在 Trae 里选的啥,强行把你所有的请求替换为该模型,比如 gpt-5.4 。 -debug :(可选)开启终端对话日志输出,可以在服务器端直观看到 AI 吐字的过程。 3. 最重要的一步:重启客户端! 脚本启动成功后, 请务必在 Windows 上关闭 Trae 客户端并重新打开(或者 Reload Window) 。只有这样,Trae Server 才会读取到我们新配置的根证书和 Hosts 劫持记录,添加大模型直接填写第三方中转站API秘钥即可,其它保持默认。 4. 安全退出 想要停止代理时,直接在终端按下 Ctrl + C 即可。 脚本内置了优雅退出机制,会自动帮你把 /etc/hosts 恢复原样,并卸载临时证书 。 附:完整 Go 语言源码 ( main.go ) package main import ( "bufio" "bytes" "context" "crypto/rand" "crypto/rsa" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/json" "encoding/pem" "flag" "fmt" "io" "log" "math/big" "net" "net/http" "net/http/httputil" "net/url" "os" "os/exec" "os/signal" "strings" "sync" "syscall" "time" ) const ( hostsFile = "/etc/hosts" hijackEntry = "127.0.0.1 api.openai.com #AddedByTraeProxy" ) var ( systemCAPath string rootCA tls.Certificate isDebug bool forceModel string ) // --- 流式数据拦截与流控 (智能防错乱版) --- type streamInterceptor struct { originalBody io.ReadCloser dataChan chan []byte errChan chan error logChan chan[]byte closeOnce sync.Once currentBuf[]byte } func newStreamInterceptor(body io.ReadCloser, logChan chan[]byte) *streamInterceptor { s := &streamInterceptor{ originalBody: body, dataChan: make(chan[]byte, 10000), errChan: make(chan error, 1), logChan: logChan, } go s.readFromUpstream() return s } func (s *streamInterceptor) readFromUpstream() { defer close(s.dataChan) for { buf := make([]byte, 4096) n, err := s.originalBody.Read(buf) if n > 0 { chunk := make([]byte, n) copy(chunk, buf[:n]) s.dataChan <- chunk } if err != nil { s.errChan <- err return } } } // 代理发往 IDE 前端的数据读取逻辑 func (s *streamInterceptor) Read(p[]byte) (int, error) { // 恢复雷打不动的物理节流:最大限制 128 字节 limit := 128 if len(p) < limit { limit = len(p) } if len(s.currentBuf) == 0 { select { case chunk, ok := <-s.dataChan: if !ok { select { case err := <-s.errChan: return 0, err default: return 0, io.EOF } } s.currentBuf = chunk } } n := len(s.currentBuf) if n > limit { n = limit } // 【核心修复:智能 UTF-8 边界识别,彻底告别半个字符乱码导致的 4054】 // UTF-8 的多字节字符后续字节特征是二进制 10xxxxxx (即 16进制的 0x80~0xBF) // 如果我们要切断的位置(index n)正好位于一个汉字的中间, // 我们向前回退(n--),直到找到完整的字符边界! if n < len(s.currentBuf) { for n > 0 && (s.currentBuf[n]&0xC0) == 0x80 { n-- } } if n == 0 { // 极小概率的安全兜底 n = 1 } // 切取并输出安全的整字符数据 copy(p, s.currentBuf[:n]) s.currentBuf = s.currentBuf[n:] if isDebug && s.logChan != nil { b := make([]byte, n) copy(b, p[:n]) select { case s.logChan <- b: default: } } // 恢复强制休眠防错乱,保障前端渲染完美顺滑 time.Sleep(10 * time.Millisecond) return n, nil } func (s *streamInterceptor) Close() error { s.closeOnce.Do(func() { if s.logChan != nil { close(s.logChan) } }) return s.originalBody.Close() } // 终端日志打印 func startStreamLogger(ch <-chan[]byte) { var buffer string fmt.Print("\n\033[36m🤖[AI回复]: \033[0m\033[32m") for data := range ch { buffer += string(data) for { idx := strings.Index(buffer, "\n") if idx == -1 { break } line := strings.TrimSpace(buffer[:idx]) buffer = buffer[idx+1:] if strings.HasPrefix(line, "data: ") { payload := strings.TrimPrefix(line, "data: ") if payload == "[DONE]" { fmt.Print("\033[0m\n\033[90m[✔️ 传输结束]\033[0m\n") continue } var v struct { Choices []struct { Delta struct { Content string `json:"content"` } `json:"delta"` } `json:"choices"` } if err := json.Unmarshal([]byte(payload), &v); err == nil && len(v.Choices) > 0 { fmt.Print(v.Choices[0].Delta.Content) } } } } fmt.Print("\033[0m") } // --- 程序主入口 --- func main() { flag.Usage = func() { fmt.Fprintf(os.Stderr, "\n🚀 Trae 代理助手\n\n") fmt.Fprintf(os.Stderr, "用法:\n") fmt.Fprintf(os.Stderr, " %s [参数]\n\n", os.Args[0]) fmt.Fprintf(os.Stderr, "参数列表:\n") flag.PrintDefaults() fmt.Fprintf(os.Stderr, "\n示例:\n") fmt.Fprintf(os.Stderr, " %s -target http://10.168.188.19:8317 -model gpt-5.4 -debug\n\n", os.Args[0]) } targetFlag := flag.String("target", "https://api.openai.com", "目标 API 地址") debugFlag := flag.Bool("debug", false, "开启调试模式(打印请求详情和AI回复)") modelFlag := flag.String("model", "", "强制指定实际请求的模型名称(覆盖前端请求)") helpFlag := flag.Bool("h", false, "显示帮助信息") flag.Parse() if *helpFlag { flag.Usage() os.Exit(0) } targetURL := *targetFlag isDebug = *debugFlag forceModel = *modelFlag remote, err := url.Parse(targetURL) if err != nil { log.Fatalf("❌ 解析目标地址失败: %v", err) } fmt.Println("=====================================================") fmt.Println("🚀 正在启动 API 代理...") fmt.Printf("🎯 目标地址 : %s\n", targetURL) if forceModel != "" { fmt.Printf("🎭 强制模型 : 已开启 (所有请求将强制转为 \033[33m%s\033[0m)\n", forceModel) } if isDebug { fmt.Println("🐛 运行模式 : 调试模式 (将打印详细日志)") } else { fmt.Println("⚡ 运行模式 : 标准模式 (精简输出)") } fmt.Println("🛑 按 Ctrl+C 可以安全停止并自动清理环境。") fmt.Println("\033[31m⚠️ 重要提示 : 代理已启动,请务必【重启或重载 Trae 客户端】使配置生效!\033[0m") fmt.Println("=====================================================") generateAndInstallCA() setupHosts() go handleGracefulShutdown() startProxyServer(remote) } // --- 核心反向代理 --- func startProxyServer(remote *url.URL) { cert := generateFakeServerCert("api.openai.com") dialer := &net.Dialer{ Timeout: 15 * time.Second, KeepAlive: 30 * time.Second, } customTransport := &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { if strings.HasPrefix(addr, "api.openai.com") { r := &net.Resolver{ PreferGo: true, Dial: func(ctx context.Context, network, address string) (net.Conn, error) { return net.Dial("udp", "8.8.8.8:53") }, } ips, err := r.LookupIPAddr(ctx, "api.openai.com") if err == nil && len(ips) > 0 { addr = fmt.Sprintf("%s:%s", ips[0].String(), strings.Split(addr, ":")[1]) } } return dialer.DialContext(ctx, network, addr) }, ForceAttemptHTTP2: true, MaxIdleConns: 100, MaxIdleConnsPerHost: 100, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } proxy := httputil.NewSingleHostReverseProxy(remote) proxy.Transport = customTransport // 【强制同步刷新】:设为与休眠时间一致的 10ms, // 防止反向代理库(ReverseProxy)自带的 32KB 缓冲将数据憋住导致超时 proxy.FlushInterval = 10 * time.Millisecond proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { if err == context.Canceled || err == io.EOF { return } if isDebug { log.Printf("❌ 代理错误: %v", err) } w.WriteHeader(http.StatusBadGateway) } originalDirector := proxy.Director proxy.Director = func(req *http.Request) { originalDirector(req) req.Host = remote.Host req.Header.Del("Accept-Encoding") if isDebug { log.Printf("📥 收到请求 -> %s", req.URL.Path) } if forceModel != "" && req.Body != nil && req.Method != http.MethodGet { bodyBytes, err := io.ReadAll(req.Body) req.Body.Close() if err == nil && len(bodyBytes) > 0 { var payload map[string]interface{} if err := json.Unmarshal(bodyBytes, &payload); err == nil { if oldModelInter, hasModel := payload["model"]; hasModel { oldModel, _ := oldModelInter.(string) payload["model"] = forceModel newBodyBytes, _ := json.Marshal(payload) req.Body = io.NopCloser(bytes.NewReader(newBodyBytes)) req.ContentLength = int64(len(newBodyBytes)) req.Header.Set("Content-Length", fmt.Sprintf("%d", len(newBodyBytes))) if isDebug { log.Printf("🔄 [模型篡改] 原始模型: %s -> 强制替换为: %s", oldModel, forceModel) } } else { req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) } } else { req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) } } } } proxy.ModifyResponse = func(resp *http.Response) error { if resp.Request != nil && resp.Request.URL.Path == "/v1/models" { bodyBytes, err := io.ReadAll(resp.Body) resp.Body.Close() var payload map[string]interface{} if err == nil && len(bodyBytes) > 0 { json.Unmarshal(bodyBytes, &payload) } if payload == nil { payload = make(map[string]interface{}) } if _, ok := payload["object"]; !ok { payload["object"] = "list" } dataInter, ok := payload["data"].([]interface{}) if !ok { dataInter =[]interface{}{} } addModel := func(modelName string) { for _, m := range dataInter { if mObj, ok := m.(map[string]interface{}); ok { if id, _ := mObj["id"].(string); id == modelName { return } } } dataInter = append(dataInter, map[string]interface{}{ "id": modelName, "object": "model", "created": time.Now().Unix(), "owned_by": "openai", }) } addModel("gpt-5") if forceModel != "" { addModel(forceModel) } payload["data"] = dataInter newBodyBytes, _ := json.Marshal(payload) resp.Body = io.NopCloser(bytes.NewReader(newBodyBytes)) resp.ContentLength = int64(len(newBodyBytes)) resp.Header.Set("Content-Length", fmt.Sprintf("%d", len(newBodyBytes))) resp.Header.Set("Content-Type", "application/json") if resp.StatusCode >= 400 { resp.StatusCode = http.StatusOK resp.Status = "200 OK" } return nil } contentType := resp.Header.Get("Content-Type") if strings.Contains(contentType, "text/event-stream") { // 流式响应强制干掉 Content-Length 头,防止客户端解析阻塞 resp.Header.Del("Content-Length") var logChan chan []byte if isDebug { logChan = make(chan[]byte, 5000) go startStreamLogger(logChan) } resp.Body = newStreamInterceptor(resp.Body, logChan) } return nil } server := &http.Server{ Addr: "127.0.0.1:443", Handler: proxy, ReadHeaderTimeout: 10 * time.Second, IdleTimeout: 120 * time.Second, TLSConfig: &tls.Config{ Certificates:[]tls.Certificate{*cert}, MaxVersion: tls.VersionTLS12, }, TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), } if isDebug { log.Println("🛡️ 服务已就绪,正在监听 127.0.0.1:443...") } if err := server.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed { log.Fatalf("❌ 服务绑定失败 (请确保使用 sudo): %v", err) } } // --- 证书管理与系统配置 (保持原样) --- func generateAndInstallCA() { priv, _ := rsa.GenerateKey(rand.Reader, 2048) serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit) caTemplate := &x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{Organization:[]string{"Proxy Local CA"}, CommonName: "Proxy Local Root CA"}, NotBefore: time.Now(), NotAfter: time.Now().AddDate(10, 0, 0), IsCA: true, ExtKeyUsage:[]x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, BasicConstraintsValid: true, } caBytes, _ := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, &priv.PublicKey, priv) certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: caBytes}) keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) rootCA, _ = tls.X509KeyPair(certPEM, keyPEM) if _, err := os.Stat("/usr/local/share/ca-certificates"); err == nil { systemCAPath = "/usr/local/share/ca-certificates/proxy_local.crt" os.WriteFile(systemCAPath, certPEM, 0644) exec.Command("update-ca-certificates").Run() } else if _, err := os.Stat("/etc/pki/ca-trust/source/anchors"); err == nil { systemCAPath = "/etc/pki/ca-trust/source/anchors/proxy_local.crt" os.WriteFile(systemCAPath, certPEM, 0644) exec.Command("update-ca-trust", "extract").Run() } } func uninstallCA() { if systemCAPath != "" { os.Remove(systemCAPath) if strings.Contains(systemCAPath, "usr/local") { exec.Command("update-ca-certificates").Run() } else { exec.Command("update-ca-trust", "extract").Run() } } } func generateFakeServerCert(host string) *tls.Certificate { caLeaf, _ := x509.ParseCertificate(rootCA.Certificate[0]) priv, _ := rsa.GenerateKey(rand.Reader, 2048) serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit) template := x509.Certificate{ SerialNumber: serialNumber, NotBefore: time.Now().Add(-24 * time.Hour), NotAfter: time.Now().Add(365 * 24 * time.Hour), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage:[]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, DNSNames:[]string{host}, } derBytes, _ := x509.CreateCertificate(rand.Reader, &template, caLeaf, &priv.PublicKey, rootCA.PrivateKey) return &tls.Certificate{Certificate: [][]byte{derBytes}, PrivateKey: priv} } func setupHosts() { hostsContent, _ := os.ReadFile(hostsFile) if !strings.Contains(string(hostsContent), "api.openai.com") { f, err := os.OpenFile(hostsFile, os.O_APPEND|os.O_WRONLY, 0644) if err == nil { f.WriteString("\n" + hijackEntry + "\n") f.Close() } } } func handleGracefulShutdown() { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) <-sigChan fmt.Println("\n🛑 收到退出信号,正在清理环境 (恢复 hosts 和证书)...") file, err := os.Open(hostsFile) if err == nil { var lines[]string scanner := bufio.NewScanner(file) for scanner.Scan() { if scanner.Text() != hijackEntry { lines = append(lines, scanner.Text()) } } file.Close() os.WriteFile(hostsFile,[]byte(strings.Join(lines, "\n")+"\n"), 0644) } uninstallCA() os.Exit(0) } 3 个帖子 - 2 位参与者 阅读完整话题

linux.do · 2026-04-16 09:33:42+08:00 · tech

# #前言 : 在我们使用ssh的时候正常情况下,如果我们退出ssh,进程会被杀掉,导致一些需要运行较长时间(比如 rsync/cp )的命令中断,十分的难受,这里提供两种解决办法: ## #1 .使用screen ### #1 .1开启 screen: screen -S upload ### #1 .2执行命令 ##举例 rsync -avh --progress /data/myfolder/ /mnt/myfolder/ ### #1 .3退出SSH保持后台运行: 按:Ctrl + A 然后按 D ### #1 .4重新连接后恢复 screen -r upload # #2 .使用tmux ### #2 .1启动tmux tmux new -s upload ### #2 .2执行命令后,退出保持后台 Ctrl + B 然后按 D ### #2 .3重新连接后恢复 tmux attach -t upload 此文章已经同步上传到我的博客: www.andy-y.cn 2 个帖子 - 1 位参与者 阅读完整话题

www.ithome.com · 2026-04-15 20:28:36+08:00 · tech

IT之家 4 月 15 日消息,微星 MSI 今日宣布面向全球市场推出其 2026 年游戏笔记本电脑,包括泰坦 (Titan / Raider)、绝影 (Stealth)、神影 (Crosshair)、星影 (Cyborg) 产品线的多款型号。 IT之家注意到,该新闻稿提到神影 Crosshair 16 Max HX (E2W) 可选搭配 12GB 显存的英伟达 GeForce RTX 5070 笔记本电脑 GPU 。不过这一型号的当前产品页面中写的仍是 "Up to GeForce RTX 5070 Laptop GPU 8GB GDDR7"。 目前尚不清楚这单纯是微星的小纰漏或是英伟达正式解禁移动端 RTX 5070 12GB 的前奏。 2026 款神影 Crosshair 16 Max HX 最薄处仅 21.95mm,相较上代薄了近 14.3%。其搭载 2 风扇 5 热管 4 出风散热系统,支持 200W 整机性能释放;配备 VESA DisplayHDR True Black 500 认证 QHD+ 165Hz 规格 OLED 屏幕。

linux.do · 2026-04-14 22:06:38+08:00 · tech

最近自己和身边的人都遇到在vscode里使用codex插件(不是cli)无法在服务器上使用,发一个貌似可复现的解决方案,给遇到同样问题的人参考。 问题现象 我本地使用 Codex / Cursor 一切正常,远程服务器也能正常 curl 到 OpenAI,CLI 版本也可以正常对话,但只要在 Remote SSH 窗口里使用插件版登录,就会报错: Sign-in could not be completed Token exchange failed: token endpoint returned status 403 Forbidden: Country, region, or territory not supported 按照github issue中部分解决方法,尝试先在本地或者cli中登录然后复制auth.json到.codex文件夹,能够进入登录态,但是对话会显示 timeout waiting for child process to exit 最终解决思路 让远程扩展通过 SSH 反向端口转发,使用本地的代理出口,而不是直接走远程服务器自己的网络出口。 预先准备: 1. 这里建议尽可能清除.codex中原有的auth等认证文件,卸载重装原有的插件等,防止环境冲突 2. VPN开启TUN模式,端口7897(可根据自己的端口号修改) 第一步:在本地 Cursor / VS Code 设置代理 打开本地设置,方法是shift+ctrl+p,输入Preferences: Open User Settings (JSON) 在本地设置里配置: { "http.proxy": "http://127.0.0.1:7897", "http.proxySupport": "on" } 第二步:在远程设置里启用“使用本地代理配置” 打开远程设置,方法是shift+ctrl+p,输入Preferences: Open Remote Settings (JSON) 连接到 Remote SSH 之后,打开 远程设置 ,加入: { "http.useLocalProxyConfiguration": true } 第三步:修改 SSH 配置,加入正向反向端口转发 在你的 SSH config 里加入: Host 你的服务器名 HostName 你的服务器IP Port 你的端口 User 你的用户名 RemoteForward 7897 127.0.0.1:7897 LocalForward 1455 127.0.0.1:1455 其中1455端口是codex认证消息返回走的端口,数值不能修改 第四步:彻底断开并重新连接 Remote SSH 第五步:重新点击 Codex 插件登录 由于时间有限,没有去探究最小改动策略,因此上面的改动可能存在 冗余操作 ,也不一定对所有人都有效,佬友们可以根据自己的情况进行尝试,希望能帮助到大家。 2 个帖子 - 1 位参与者 阅读完整话题

hnrss.org · 2026-04-14 10:51:14+08:00 · tech

prmana replaces static SSH keys with short-lived OIDC tokens validated at the host through PAM. What makes it different from other OIDC-for-SSH approaches is DPoP (RFC 9449) — every authentication includes a cryptographic proof that the token holder has the private key. Stolen tokens can't be replayed. Three components: a PAM module (pam_prmana.so), a client agent (prmana-agent), and a shared OIDC/JWKS library (prmana-core). All Rust. DPoP keys can be software, YubiKey (PKCS#11), or TPM 2.0. No gateway, no SSH CA, no patches to sshd. Standard ssh client, standard sshd, PAM in between. Tested against Keycloak, Auth0, Google, and Entra ID. The name is from Sanskrit — pramana (प्रमाण) means "proof." Comments URL: https://news.ycombinator.com/item?id=47760680 Points: 3 # Comments: 0