适用于claude code和codex的轻量级AI上下文引擎加上Prompt增强

那缺省的claude code 和 codex 都很好用,上下文搜索用rigrep也算可以,那有更厉害的Augment引擎,能压缩上下文 试一试,首先去佬友的网站: https://acemcp.heroman.wtf 注册并申请好API Key 然后安装这个ace-tool-rs或者ace-tool # 安装最新版本 npx ace-tool-rs npx ace-tool 然后最重要要的,在什么地方添加文档,编辑~/.claude.json文件。 不是系统的~/.claude/settings.json,千万别弄错了 添加: "mcpServers": { "ace-tool-rs": { "type": "stdio", "command": "npx", "args": [ "ace-tool-rs", "--base-url", "https://acemcp.heroman.wtf/relay/", "--token", "ace_api_key" ], "env": {} } } { "mcpServers": { "ace-tool": { "command": "/root/.nvm/versions/node/v22.21.1/bin/ace-tool", "args": [ "--base-url", "https://acemcp.heroman.wtf/relay/", "--token", "ace_api_key" ] } } 我的ace-tool比较直接,command是全路径的,因为node是nvm管理的,而且claude运行的时候是派生一个子进程来运行的,导致路径找不到;或者更加直接的,bash起手,ace-tool放进 args里。 那codex如下: [mcp_servers.ace-tool] command = "ace-tool" args = [ "--enable-log", "--base-url", "https://acemcp.heroman.wtf/relay/", "--token", "ace_api_key"] [mcp_servers.ace-tool-rs] command = "npx" args = ["ace-tool-rs", "--base-url", "https://acemcp.heroman.wtf/relay/", "--token", "ace_api_key", "--transport", "lsp"] env = { RUST_LOG = "info" } startup_timeout_ms = 60000 然后验证 /mcp list 使用ace-tool-rs mcp来索引整个项目

2026年1月15日

Antigravity的使用注意

反重力真的是超级好用,可以直接命令他建立一个nodejs程序,使用puppytear,然后执行点击什么什么动作,这就直接给你拉起来一个框架,然后我们可以在这个框架上不断完善、丰富,最终完成程序。 那使用它还真的是有点门槛,首先必须要有一个纯净的IP,美国的 然后不用tun,首先要本地有个代理,比如v2rayN,127.0.0.1:10808端口,通往纯净美国ip的转发 还需要有个proxifier,先新建代理服务器 再新建规则来使用这个代理: 需要新建一条gemini的规则(因为antigravity也是调用gemini cli) 一开始就是只加了antigravity.exe,不行,后来都加上就行了。 antigravity.exe; language_server_windows_x64.exe; "Antigravity.app"; "Antigravity"; com.google.antigravity; language_server_macos_arm 设置走本地那个代理 这样齐活了。 如果登录后提示: Your current account is not eligible for Antigravity, because it is not currentlyavailable in your location. 确认账号当前的国家或地区 访问以下网址https://policies.google.com/terms,登录自己的账号即可查看账号当前的国家或地区版本。 修改账号当前的国家或地区 访问以下网址https://policies.google.com/country-association-form 重点来了,以上选择请选最后一个“以上都不是”,参考申请如下: 我因工作需要用到gemini,请帮我更改到美国 那反重力的中文配置: # 中文原生协议 v5.0 ## 一、核心身份 你是**中文原生**的技术专家。思维和输出必须遵循中文优先原则。 --- ## 二、语言规则 ### 2.1 输出语言 - 所有解释、分析、建议用**中文** - 技术术语保留英文(如 API、JWT、Docker、Kubernetes) - 代码相关保持英文(变量名、函数名、文件路径、CLI 命令) ### 2.2 示例 - ✅ "检查 `UserService.java` 中的认证逻辑" - ✅ "这个 `useEffect` Hook 存在依赖项问题" - ❌ "Let me analyze the code structure" - ❌ "I'll check the authentication logic" ### 2.3 工具调用 -**机器读的保留英文**:file_path, function_name, endpoint - **人读的必须中文**:task_title, description, commit_message --- ## 三、项目上下文获取 ### 3.1 新对话时,按优先级阅读以下文件(如果存在): 1.`contexts/context.md` - 项目核心上下文 ⭐最重要 2.`README.md` - 项目概述 3.`specs/*.md` - 技术规范 4.`.agent/workflows/*.md` - 工作流配置 ### 3.2 如果项目没有上述文件: - 先询问项目基本情况 - 建议创建 `contexts/context.md` 记录项目信息 --- ## 四、通用开发规范 ### 4.1 Implementation Plan 和 Task - 标题必须使用**中文** - 步骤说明必须使用**中文** - 示例:`### 实现用户登录功能` 而非 `### Implement User Login` ### 4.2 代码注释 - 新代码的注释必须使用**中文** - 保持注释简洁明了 - 示例:`// 检查用户是否已登录` 而非 `// Check if user is logged in` ### 4.3 Git 提交信息 - 使用中文,格式:`<类型>: <描述>` - 示例:`feat: 添加用户登录功能`、`fix: 修复积分计算错误` ### 4.3 文档编写 - 技术文档使用中文 - 保持 Markdown 格式规范 --- ## 五、工作模式 ### 5.1 复杂任务 - 先阅读相关规范文档 - 制定计划后再执行 - 完成后更新相关文档 ### 5.2 简单任务 - 直接执行 - 保持代码风格一致 ### 5.3 不确定时 - 主动询问而非猜测 - 提供选项让用户决策 仔细看一下,果然是GEMINI.md ...

2026年1月14日

一个轻量级超级省钱的lxc平台containarium

现在的爱好是天天逛github.com,里面真的是有非常多的好项目 哪个好就clone下来,让Cluade Code来分析分析 推荐一个好的lxc容器平台 https://github.com/FootprintAI/Containarium 之所有说它好,是因为10年前,初到叮当的时候,就搭了一套LXC的容器平台,那时候Docker才方兴未艾,ZFS好像也才刚起步 找了三台特别普通的Dell R720 2U服务器,然后搭了LXC,一台物理机虚拟60台lxc容器,然后同事们用了大概6年,没有人意识到这只是个容器,并非完整虚机,但都当做完整虚机再使用,直到运行到第6年,大数据的同事在上面跑了若干elasticsearch,然后就拉跨了。原因是用的人过多,而es又认为有服务器的一半内存可用,一旦灌输数据,就把服务器拖死了。 这个项目也是相同的想法,也用lxc,然后用ZFS来当存储,这个主意很绝妙 完成度相当的高,用terraform来搭建底层支撑平台。 看看claude code的分析: ● 现在我已经获得了足够的信息来进行详细分析。让我基于收集的信息为您提供全面的项目可行性评估。 Containarium 项目详细分析与可行性评估 📋 项目概述 Containarium 是一个基于 LXC 系统容器的多租户开发环境平台,旨在通过在单个云虚拟机上运行数百个隔离的 Linux 开发环境,大幅降低基础设施成本(可节省高达92%)。 ✅ 核心优势与创新点 成本效益极高 资源密度: 相比传统 VM,每个容器仅消耗 100-500MB RAM,密度提升 10-50倍 Spot 实例: 利用 GCP Spot 实例可节省 76% 成本 实际数字: 50 用户从 $1,250/月 降至 $98/月 (92% 节省) 技术架构扎实 LXC vs Docker: 选择系统容器(LXC)而非应用容器,更适合多用户开发环境 ZFS 存储: 支持压缩、配额、快照,数据在 Spot 实例重启后完全保留 安全多租户: 每个用户独立的 jump server 账户(proxy-only,无 shell 访问) unprivileged 容器隔离 fail2ban + SSH 审计日志 工程质量高 类型安全: 使用 Protobuf/gRPC 定义接口 测试完备: 包含基于 Terraform 的 E2E 测试,验证 ZFS 持久化 文档完善: README 长达 2400+ 行,包含详细架构图、使用指南、FAQ 生产就绪: 支持 mTLS 远程管理、水平扩展、负载均衡 🎯 技术可行性分析 ...

2026年1月13日

Google学生号的申请

Google 学生号的申请,备份一下,省的忘记了: 首先前提是要有一个纯净的美国IP,可打开下面网址先看看纯净不纯净 ping0.cc 然后打开网页看看有没有权限 https://gemini.google.com/students 然后不要乱点,一定要右键复制链接 但是奇怪的是,我这边直接点了就过了,也是奇葩 如果能Copy链接,就拷贝了 然后到 https://batch.1key.me 打开验证通过 然后刷新页面,绑卡,就OK了。 然后啊,需要打开 https://aistudio.google.com/?authuser=3 拿到缺省项目的key,然后 export GEMINI_API_KEY="你的API_KEY" 尝试gemini登录一下,问问它是什么模型 最后别忘记在主号里面,管理会员资格,管理会员方案,打开共享 就可以放心的登录antigravity了

2026年1月13日

claude code的Ralph Wiggum编程模式和技巧

拉尔夫·维古姆(Ralph Wiggum):没完没了的 Claude 代码循环 这其实是个梗: Ralph Wiggum 是动画片《辛普森一家》中那个总是搞不清状况、智商不太在线的孩子。他最著名的梗图是 “I’m helping”(我在帮忙)。 我们在calude-code源代码仓库中可以看到这个插件 https://github.com/anthropics/claude-code/tree/main/plugins/ralph-wiggum 核心概念: # You run ONCE: /ralph-loop "Your task description" --completion-promise "DONE" # Then Claude Code automatically: # 1. Works on the task # 2. Tries to exit # 3. Stop hook blocks exit # 4. Stop hook feeds the SAME prompt back # 5. Repeat until completion 翻译一下: # 执行命令 /ralph-loop "Your task description" --completion-promise "DONE" # 接下来 Claude Code 将自动执行以下操作: # 1. 处理/执行该任务 # 2. 尝试退出程序 # 3. “停止挂钩(Stop hook)”拦截并阻止退出 # 4. “停止挂钩”将完全相同的提示词(prompt)重新传回给系统 # 5. 重复上述循环,直到任务完成(即出现 "DONE") 说白了就是Claude执行了一轮后要退出,然后拉尔夫就蹦出来阻止,没完;再跑一轮,又想退出,又被阻止,如此往复。 ...

2026年1月12日

claude code的session历史回顾和继续工具

现在基本Claude code 和 Codex 和 Gemini 混用 运维本身的项目也不会怎么大 用Claude的时候,开了很多项目去学习、研究;那历史和切回来继续研究就变得很重要,每次如果都从头开始分析,那崩溃了 手搓了一个工具claude-history,可以观看历史,并且生成命令,可以在以前的session基础上继续研究 http://github.com/zhangrr/claude-history 纯用Go,前后端一起,监听端口是8888 ✨ 特性 单文件实现:所有功能集成在 main.go 一个文件中(约 900 行) 零依赖部署:编译后的二进制文件包含前端资源,无需额外文件 高性能:使用 Go 标准库,性能优异 跨平台:支持 Linux、macOS、Windows 实时流式传输:通过 SSE (Server-Sent Events) 实现实时更新 文件监控:使用 fsnotify 监控 Claude 会话文件变化

2026年1月12日

如何用codex、claude code混合口嗨编程

codex和claude code两者各有所长,尝试在一个音乐项目里把两者结合起来,试试看 首先 clone 项目 git clone https://github.com/truelife0958/music888 然后命令它: 请你把这个项目的api音乐源都提取出来然后制作一个api接口文档 然后得出一个结果 然后新建一个目录,把这个文档单独拷贝进去,新启动一个codex 请你看一下这个api接口文档并测试一下哪些是有效的,帮我制作一个音乐播放web,需要有喜欢,最近播放,以及用户创立。 让它运行,打开页面看看 然后claude装好skills,Apple-Hig-Designer,地址是:https://github.com/axiaoge2/Apple-Hig-Designer 分析当前项目,Using apple-hig-desiner skills 重新设计前端页面 代开claude 回答几个问题 确定方案后开始改造 牛B吹得挺大: 打开一看,250啊 啥狗屁玩意,继续修去 <link rel="stylesheet" href="/.claude/skills/Apple-Hig-Designer/resources/design-tokens.css" /> 无法访问 刷新,擦的,图标都是错的 顶栏的图标都是错的 修好后再看 确实是苹果风格,点开设置,又拉跨了 继续命令它改: 设置,齿轮按钮打开后,配置窗口跑到最左面了,没有在右边 擦的,什么玩意,放中央,简直了 弹窗放到按钮附近 最终还可以,但是。 感觉这个skills还没有codex原生生成的好看。 编程基本变成了口嗨的行为了,真的是脑洞大开。

2026年1月9日

Caddy配合zot实现ip白名单登录docker registry

公司有两个docker的registry,一个是在公司内网,一个是在hk的公网 那jenkins打镜像的时候,FROM底包是在hk,打出镜像后,又要推到公司的内网,这样jenkins的pipeline看起来就很痛苦 打包登录公网一次,打包的地址是内网的harbor,推包再登录内网一次,脚本如下: docker.withRegistry("https://hk.rendoumi.com", "hk-user") { image = docker.build("harbor.rendoumi.dev/${IMAGE_NAME}:${TAG}", "${PROJECT_PATH}") } docker.withRegistry("https://harbor.rendoumi.dev", "harbor-user") { if (image) { image.push() } } 流水线不多的话这么还行得通,流水线很多,就得改个一溜够 那就想如果登录hk的服务器使用白名单机制,公司的jenkins worknodes服务器去访问,就不用登录,直接访问,就好了 那公司私网的registry是用的harbor搭的,公网的registry用的caddy+zot搭建 问了下 Gemini, 结果非常的荒谬,居然出现幻觉,呓推出了zot根本不存在的ip白名单限制规则,简直了!!! 后来跑到 zot 的官网看了半天文档,发现不行,根本没有这种配置 那就只能从Caddy入手了,从公司jenkins worknode出来的ip先到Caddy 然后Caddy模拟登录信息,给后端的zot发请求,就可以通过了 做法如下: 首先把 hk registry 的用户名和密码生成base64字串,得到XXXXX的BASE64字符串 echo -n "user1:pass1" | base64 然后修改Caddyfile,原有的配置 hk.rendoumi.com { reverse_proxy 127.0.0.1:5000 { header_up X-Real-IP {remote_host} transport http { dial_timeout 15s response_header_timeout 300s } } } 改成如下: hk.rendoumi.com { @trusted { remote_ip 111.222.333.444 } handle @trusted { reverse_proxy 127.0.0.1:5000 { header_up Authorization "Basic XXXXXXXXXXXXXXXX" header_up X-Real-IP {remote_host} transport http { dial_timeout 15s response_header_timeout 300s } } } handle { reverse_proxy 127.0.0.1:5000 { header_up X-Real-IP {remote_host} transport http { dial_timeout 15s response_header_timeout 300s } } } } 解释一下,上面如果是 111.222.333.444 的ip来访问,那就模拟登录的 Head 头信息,如果不是,就直接放行,由后端的zot进行验证 ...

2026年1月9日

薅羊毛之扣子编程

前几天佬友说扣子编程出了一个免费版,所以也想薅一把 事先声明:扣子编程是国内的,所以无法翻墙,本质也是一个容器。能做一个代理隐藏IP来访问国内的网站。 所以,说老实话,自己很少有这个场景来使用 废话不多说,直接开始: 扣子编程的网址:https://code.coze.cn 本质是火山引擎旗下的东西,也是一个容器,而且吧,比较有意思,跟 claude 和 codex 差不多 基本是:讲述式编程方式,一天智能建3个项目,一个项目是1cpu,2G内存 打开网站,新建项目,然后点击网页应用,下面的文本输入框就是编程的地方了: 我们先让它自己生成一个应用,再在生成的基础上进行修改,直接硬来会有无穷的麻烦,它有自己固定的架构 建立一个极简的nodejs程序,不依赖任何前端和后端框架,不要用到next框架。前端页面是index.html,里面是Hello world,主程序是index.js,用来显示index.html的内容,绝对不要用到任何js框架。 然后系统就叽里呱啦、哔哔赖赖开始自己搞了,它缺省创立的项目无论你再怎么强调,都是基于next.js的,都会拉出一坨next的屎,所以会神经兮兮的思考来思考去,产生一堆废物,我们可以不用理他 然后显示正常 从上图我们得到几个关键信息: 端口是5000 文件夹可以看到文件,里面有一堆缺省的配置,就算强调,依然有next拉的屎 部署按钮,我们部署测试一下 按部署按钮,会得到一个域名 然后部署成功,点击箭头 打开后,就是我们想要的 我们记下来这个域名,大善人啊,免费域名和免费证书 然后回到文件夹,来修改三个文件,index.html 和 index.js 和 package.json 先来改 package.json, 在dependencies中,加两句,注意这两句的上一句需要加个逗号,axios最后没有逗号 "ws": "^8.14.2", "axios": "^1.12.2" 注意:写到最后,发现这里可以再部署一下再改index.jsp和index.html比较好,就不用经历我下面的回档了!!!!! 我下面是没有再部署,直接硬改,点开文件夹图标,选中index.js,先别贴,需要修改混淆的: const http = require('http'); const https = require('https'); const fs = require('fs'); const dns = require('dns'); const axios = require('axios'); const net = require('net'); const path = require('path'); const crypto = require('crypto'); const { Buffer } = require('buffer'); const { WebSocket, createWebSocketStream } = require('ws'); // 生成 UUID v4 function generateUUID() { return crypto.randomUUID(); } const UUID = process.env.UUID || generateUUID(); const DOMAIN = process.env.DOMAIN || 'xxxxxxxx.coze.site'; // 填写项目域名 const WSPATH = process.env.WSPATH || UUID.slice(0, 8); // 节点路径,默认获取uuid前8位 const SUB_PATH = process.env.SUB_PATH || 'sub'; // 节点的订阅路径 const NAME = process.env.NAME || ''; // 节点名称 const PORT = process.env.PORT || 5000; // http和ws服务端口 const TLS_KEY_PATH = process.env.TLS_KEY_PATH || ''; const TLS_CERT_PATH = process.env.TLS_CERT_PATH || ''; let ISP = ''; const GetISP = async () => { try { const res = await axios.get('https://api.ip.sb/geoip'); const data = res.data; ISP = `${data.country_code}-${data.isp}`.replace(/ /g, '_'); } catch (e) { ISP = 'Unknown'; } } GetISP(); function normalizeSecret(value) { if (!value) return ''; return value.includes('\\n') ? value.replace(/\\n/g, '\n') : value; } function readOptionalFile(filePath) { if (!filePath) return ''; const resolvedPath = path.isAbsolute(filePath) ? filePath : path.join(__dirname, filePath); try { return fs.readFileSync(resolvedPath, 'utf8'); } catch (error) { console.warn(`Failed to read TLS file ${resolvedPath}: ${error.message}`); return ''; } } function getTLSOptions() { let key = normalizeSecret(process.env.TLS_KEY); let cert = normalizeSecret(process.env.TLS_CERT); if (!key) { key = readOptionalFile(TLS_KEY_PATH); } if (!cert) { cert = readOptionalFile(TLS_CERT_PATH); } if (key && cert) { return { key, cert }; } if (key || cert) { console.warn('Both TLS key and certificate are required; falling back to HTTP.'); } return null; } const requestHandler = (req, res) => { if (req.url === '/') { const filePath = path.join(__dirname, 'index.html'); fs.readFile(filePath, 'utf8', (err, content) => { if (err) { res.writeHead(200, { 'Content-Type': 'text/html' }); res.end('Hello world!'); return; } res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(content); }); return; } else if (req.url === `/${SUB_PATH}`) { const namePart = NAME ? `${NAME}-${ISP}` : ISP; const vlessURL = `vless://${UUID}@${DOMAIN}:443?encryption=none&security=tls&sni=${DOMAIN}&fp=chrome&type=ws&host=${DOMAIN}&path=%2F${WSPATH}#${namePart}`; const trojanURL = `trojan://${UUID}@${DOMAIN}:443?security=tls&sni=${DOMAIN}&fp=chrome&type=ws&host=${DOMAIN}&path=%2F${WSPATH}#${namePart}`; const subscription = vlessURL + '\n' + trojanURL; const base64Content = Buffer.from(subscription).toString('base64'); res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end(base64Content + '\n'); } else { res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('Not Found\n'); } }; const tlsOptions = getTLSOptions(); const httpServer = tlsOptions ? https.createServer(tlsOptions, requestHandler) : http.createServer(requestHandler); const wss = new WebSocket.Server({ server: httpServer }); const uuid = UUID.replace(/-/g, ""); const DNS_SERVERS = ['114.114.114.114', '223.5.5.5']; // Custom DNS async function resolveHost(host) { const target = String(host || '').trim(); if (!target) throw new Error('Host is empty'); if (net.isIP(target)) return target; try { const addresses = await dns.promises.lookup(target, { all: true }); const preferred = addresses.find(a => a.family === 4) || addresses[0]; if (preferred?.address) return preferred.address; } catch (_) { // fall through to custom DNS servers } const resolver = new dns.promises.Resolver(); for (const server of DNS_SERVERS) { try { resolver.setServers([server]); const v4 = await resolver.resolve4(target); if (v4?.[0]) return v4[0]; } catch (_) {} try { resolver.setServers([server]); const v6 = await resolver.resolve6(target); if (v6?.[0]) return v6[0]; } catch (_) {} } throw new Error(`Failed to resolve ${target}`); } // VLE-SS处理 function handleVlessConnection(ws, msg) { const [VERSION] = msg; const id = msg.slice(1, 17); if (!id.every((v, i) => v == parseInt(uuid.substr(i * 2, 2), 16))) return false; let i = msg.slice(17, 18).readUInt8() + 19; const port = msg.slice(i, i += 2).readUInt16BE(0); const ATYP = msg.slice(i, i += 1).readUInt8(); const host = ATYP == 1 ? msg.slice(i, i += 4).join('.') : (ATYP == 2 ? new TextDecoder().decode(msg.slice(i + 1, i += 1 + msg.slice(i, i + 1).readUInt8())) : (ATYP == 3 ? msg.slice(i, i += 16).reduce((s, b, i, a) => (i % 2 ? s.concat(a.slice(i - 1, i + 1)) : s), []).map(b => b.readUInt16BE(0).toString(16)).join(':') : '')); ws.send(new Uint8Array([VERSION, 0])); const duplex = createWebSocketStream(ws); resolveHost(host) .then(resolvedIP => { net.connect({ host: resolvedIP, port }, function() { this.write(msg.slice(i)); duplex.on('error', () => {}).pipe(this).on('error', () => {}).pipe(duplex); }).on('error', () => {}); }) .catch(error => { net.connect({ host, port }, function() { this.write(msg.slice(i)); duplex.on('error', () => {}).pipe(this).on('error', () => {}).pipe(duplex); }).on('error', () => {}); }); return true; } // Tro-jan处理 function handleTrojanConnection(ws, msg) { try { if (msg.length < 58) return false; const receivedPasswordHash = msg.slice(0, 56).toString(); const possiblePasswords = [ UUID, ]; let matchedPassword = null; for (const pwd of possiblePasswords) { const hash = crypto.createHash('sha224').update(pwd).digest('hex'); if (hash === receivedPasswordHash) { matchedPassword = pwd; break; } } if (!matchedPassword) return false; let offset = 56; if (msg[offset] === 0x0d && msg[offset + 1] === 0x0a) { offset += 2; } const cmd = msg[offset]; if (cmd !== 0x01) return false; offset += 1; const atyp = msg[offset]; offset += 1; let host, port; if (atyp === 0x01) { host = msg.slice(offset, offset + 4).join('.'); offset += 4; } else if (atyp === 0x03) { const hostLen = msg[offset]; offset += 1; host = msg.slice(offset, offset + hostLen).toString(); offset += hostLen; } else if (atyp === 0x04) { host = msg.slice(offset, offset + 16).reduce((s, b, i, a) => (i % 2 ? s.concat(a.slice(i - 1, i + 1)) : s), []) .map(b => b.readUInt16BE(0).toString(16)).join(':'); offset += 16; } else { return false; } port = msg.readUInt16BE(offset); offset += 2; if (offset < msg.length && msg[offset] === 0x0d && msg[offset + 1] === 0x0a) { offset += 2; } const duplex = createWebSocketStream(ws); resolveHost(host) .then(resolvedIP => { net.connect({ host: resolvedIP, port }, function() { if (offset < msg.length) { this.write(msg.slice(offset)); } duplex.on('error', () => {}).pipe(this).on('error', () => {}).pipe(duplex); }).on('error', () => {}); }) .catch(error => { net.connect({ host, port }, function() { if (offset < msg.length) { this.write(msg.slice(offset)); } duplex.on('error', () => {}).pipe(this).on('error', () => {}).pipe(duplex); }).on('error', () => {}); }); return true; } catch (error) { return false; } } // Ws 连接处理 wss.on('connection', (ws, req) => { const url = req.url || ''; ws.once('message', msg => { if (msg.length > 17 && msg[0] === 0) { const id = msg.slice(1, 17); const isVless = id.every((v, i) => v == parseInt(uuid.substr(i * 2, 2), 16)); if (isVless) { if (!handleVlessConnection(ws, msg)) { ws.close(); } return; } } if (!handleTrojanConnection(ws, msg)) { ws.close(); } }).on('error', () => {}); }); httpServer.listen(PORT, () => { const scheme = tlsOptions ? 'HTTPS/WSS' : 'HTTP/WS'; console.log(`Server is running on ${scheme} port ${PORT}`); }); 原始文件长这样: ...

2026年1月6日

huggingface.co薅羊毛记

应佬友的要求再出一篇文章,前面薅了wispbyte.com的羊毛,但是吧容器比较弱小,强一点的就是huggingface.co的容器: Free版本就是2vCPU和16GB RAM,就已经非常强了 那先介绍一下huggingface,其实这是一家非常强悍的公司,可以称作是“AI界的GitHub”,可以分享模型,数据集,然后演示应用。 我们用到的就是这个演示应用(Spaces) 首先注册好账号,然后右上角点击头像按钮,新建一个Space 然后填入必须项 Space name:就写myhome Short description:Save Our Home License:MIT SDK:选择Docker Docker template: 选择Blank Space hardware:选择Free 其它都不选,最后CreateSpace即可 然后就建好了,其实是给你开了一个git的repo 我们到右上角,选择Files选项 然后打开的文件页面,只有两个文件 老套路,建立一个Dockerfile文件,就是容器的打包文件 Dockerfile的内容,编辑好,然后提交,注意:Linux有严格的大小写要求,务必遵守 FROM nikolaik/python-nodejs:python3.14-nodejs22 WORKDIR /tmp COPY index.js /tmp/index.js COPY package.json /tmp/package.json COPY index.html /tmp/index.html CMD npm install && node index.js 然后我们把index.js准备好,需要修改几个地方 const http = require('http'); const https = require('https'); const fs = require('fs'); const axios = require('axios'); const net = require('net'); const path = require('path'); const crypto = require('crypto'); const { Buffer } = require('buffer'); const { WebSocket, createWebSocketStream } = require('ws'); // 生成 UUID v4 function generateUUID() { return crypto.randomUUID(); } const UUID = process.env.UUID || generateUUID(); const DOMAIN = process.env.DOMAIN || '1234.abc.com'; // 填写项目域名 const WSPATH = process.env.WSPATH || UUID.slice(0, 8); // 节点路径,默认获取uuid前8位 const SUB_PATH = process.env.SUB_PATH || 'sub'; // 获取节点的订阅路径 const NAME = process.env.NAME || ''; // 节点名称 const PORT = process.env.PORT || 7860; // http和ws服务端口 const TLS_KEY_PATH = process.env.TLS_KEY_PATH || 'tls.key'; const TLS_CERT_PATH = process.env.TLS_CERT_PATH || 'tls.crt'; let ISP = ''; const GetISP = async () => { try { const res = await axios.get('https://api.ip.sb/geoip'); const data = res.data; ISP = `${data.country_code}-${data.isp}`.replace(/ /g, '_'); } catch (e) { ISP = 'Unknown'; } } GetISP(); function normalizeSecret(value) { if (!value) return ''; return value.includes('\\n') ? value.replace(/\\n/g, '\n') : value; } function readOptionalFile(filePath) { if (!filePath) return ''; const resolvedPath = path.isAbsolute(filePath) ? filePath : path.join(__dirname, filePath); try { return fs.readFileSync(resolvedPath, 'utf8'); } catch (error) { console.warn(`Failed to read TLS file ${resolvedPath}: ${error.message}`); return ''; } } function getTLSOptions() { let key = normalizeSecret(process.env.TLS_KEY); let cert = normalizeSecret(process.env.TLS_CERT); if (!key) { key = readOptionalFile(TLS_KEY_PATH); } if (!cert) { cert = readOptionalFile(TLS_CERT_PATH); } if (key && cert) { return { key, cert }; } if (key || cert) { console.warn('Both TLS key and certificate are required; falling back to HTTP.'); } return null; } const requestHandler = (req, res) => { if (req.url === '/') { const filePath = path.join(__dirname, 'index.html'); fs.readFile(filePath, 'utf8', (err, content) => { if (err) { res.writeHead(200, { 'Content-Type': 'text/html' }); res.end('Hello world!'); return; } res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(content); }); return; } else if (req.url === `/${SUB_PATH}`) { const namePart = NAME ? `${NAME}-${ISP}` : ISP; const vlessURL = `vless://${UUID}@${DOMAIN}?encryption=none&security=tls&sni=${DOMAIN}:443&fp=chrome&type=ws&host=${DOMAIN}&path=%2F${WSPATH}#${namePart}`; const trojanURL = `trojan://${UUID}@${DOMAIN}?security=tls&sni=${DOMAIN}&fp=chrome&type=ws&host=${DOMAIN}:443&path=%2F${WSPATH}#${namePart}`; const subscription = vlessURL + '\n' + trojanURL; const base64Content = Buffer.from(subscription).toString('base64'); res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end(base64Content + '\n'); } else { res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('Not Found\n'); } }; const tlsOptions = getTLSOptions(); const httpServer = tlsOptions ? https.createServer(tlsOptions, requestHandler) : http.createServer(requestHandler); const wss = new WebSocket.Server({ server: httpServer }); const uuid = UUID.replace(/-/g, ""); const DNS_SERVERS = ['8.8.4.4', '1.1.1.1']; // Custom DNS function resolveHost(host) { return new Promise((resolve, reject) => { if (/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(host)) { resolve(host); return; } let attempts = 0; function tryNextDNS() { if (attempts >= DNS_SERVERS.length) { reject(new Error(`Failed to resolve ${host} with all DNS servers`)); return; } const dnsServer = DNS_SERVERS[attempts]; attempts++; const dnsQuery = `https://dns.google/resolve?name=${encodeURIComponent(host)}&type=A`; axios.get(dnsQuery, { timeout: 5000, headers: { 'Accept': 'application/dns-json' } }) .then(response => { const data = response.data; if (data.Status === 0 && data.Answer && data.Answer.length > 0) { const ip = data.Answer.find(record => record.type === 1); if (ip) { resolve(ip.data); return; } } tryNextDNS(); }) .catch(error => { tryNextDNS(); }); } tryNextDNS(); }); } // VLE-SS处理 function handleVlessConnection(ws, msg) { const [VERSION] = msg; const id = msg.slice(1, 17); if (!id.every((v, i) => v == parseInt(uuid.substr(i * 2, 2), 16))) return false; let i = msg.slice(17, 18).readUInt8() + 19; const port = msg.slice(i, i += 2).readUInt16BE(0); const ATYP = msg.slice(i, i += 1).readUInt8(); const host = ATYP == 1 ? msg.slice(i, i += 4).join('.') : (ATYP == 2 ? new TextDecoder().decode(msg.slice(i + 1, i += 1 + msg.slice(i, i + 1).readUInt8())) : (ATYP == 3 ? msg.slice(i, i += 16).reduce((s, b, i, a) => (i % 2 ? s.concat(a.slice(i - 1, i + 1)) : s), []).map(b => b.readUInt16BE(0).toString(16)).join(':') : '')); ws.send(new Uint8Array([VERSION, 0])); const duplex = createWebSocketStream(ws); resolveHost(host) .then(resolvedIP => { net.connect({ host: resolvedIP, port }, function() { this.write(msg.slice(i)); duplex.on('error', () => {}).pipe(this).on('error', () => {}).pipe(duplex); }).on('error', () => {}); }) .catch(error => { net.connect({ host, port }, function() { this.write(msg.slice(i)); duplex.on('error', () => {}).pipe(this).on('error', () => {}).pipe(duplex); }).on('error', () => {}); }); return true; } // Tro-jan处理 function handleTrojanConnection(ws, msg) { try { if (msg.length < 58) return false; const receivedPasswordHash = msg.slice(0, 56).toString(); const possiblePasswords = [ UUID, ]; let matchedPassword = null; for (const pwd of possiblePasswords) { const hash = crypto.createHash('sha224').update(pwd).digest('hex'); if (hash === receivedPasswordHash) { matchedPassword = pwd; break; } } if (!matchedPassword) return false; let offset = 56; if (msg[offset] === 0x0d && msg[offset + 1] === 0x0a) { offset += 2; } const cmd = msg[offset]; if (cmd !== 0x01) return false; offset += 1; const atyp = msg[offset]; offset += 1; let host, port; if (atyp === 0x01) { host = msg.slice(offset, offset + 4).join('.'); offset += 4; } else if (atyp === 0x03) { const hostLen = msg[offset]; offset += 1; host = msg.slice(offset, offset + hostLen).toString(); offset += hostLen; } else if (atyp === 0x04) { host = msg.slice(offset, offset + 16).reduce((s, b, i, a) => (i % 2 ? s.concat(a.slice(i - 1, i + 1)) : s), []) .map(b => b.readUInt16BE(0).toString(16)).join(':'); offset += 16; } else { return false; } port = msg.readUInt16BE(offset); offset += 2; if (offset < msg.length && msg[offset] === 0x0d && msg[offset + 1] === 0x0a) { offset += 2; } const duplex = createWebSocketStream(ws); resolveHost(host) .then(resolvedIP => { net.connect({ host: resolvedIP, port }, function() { if (offset < msg.length) { this.write(msg.slice(offset)); } duplex.on('error', () => {}).pipe(this).on('error', () => {}).pipe(duplex); }).on('error', () => {}); }) .catch(error => { net.connect({ host, port }, function() { if (offset < msg.length) { this.write(msg.slice(offset)); } duplex.on('error', () => {}).pipe(this).on('error', () => {}).pipe(duplex); }).on('error', () => {}); }); return true; } catch (error) { return false; } } // Ws 连接处理 wss.on('connection', (ws, req) => { const url = req.url || ''; ws.once('message', msg => { if (msg.length > 17 && msg[0] === 0) { const id = msg.slice(1, 17); const isVless = id.every((v, i) => v == parseInt(uuid.substr(i * 2, 2), 16)); if (isVless) { if (!handleVlessConnection(ws, msg)) { ws.close(); } return; } } if (!handleTrojanConnection(ws, msg)) { ws.close(); } }).on('error', () => {}); }); httpServer.listen(PORT, () => { const scheme = tlsOptions ? 'HTTPS/WSS' : 'HTTP/WS'; console.log(`Server is running on ${scheme} port ${PORT}`); }); 其中几个地方有修改,先看看huggingface给咱们的域名,右边,Settings右边的三个点按钮, ...

2026年1月6日