给纯文本大模型装上”眼睛”:modlens 接入 + Gemini 多 Key 自动轮换实战

从”装不上、读不出、额度秒光”,到”全自动识图 + 多 Key 自动轮换”的完整记录。
环境:Windows + DeepSeek Harness (dsh, web profile),模型为纯文本的 deepseek-v4-flash。


TL;DR

  • 在 dsh 上,modlens 不是 skill,而是原生插件——装 skill 没用,一句话装插件即可
  • 图片读取走 Gemini 视觉引擎,配置在 ~/.modlens/config.json,所有 harness 共享
  • 踩了 4 类坑:直连失败 / 间歇 500/503 / 429 配额 / 代理节点地区限制
  • modlens 不支持多 Key,于是自研了 key 池 + 429 自动轮换 + 升级重打补丁三件套
  • 轮换逻辑打进插件引擎,一次修改覆盖 GUI 粘贴、工具调用、命令行所有入口
  • 已开源:https://github.com/retr67/modlens-key-rotation

1. 缘起:纯文本模型看不见图

当前会话的模型是纯文本输入(deepseek-v4-flash)。想让 Agent 分析截图、图表、扫描件,直接喂图片是喂不进去的——需要一层”视觉桥接”把图片变成模型能读的文字。

这就是 modlens 干的事:把图片转成结构化 JSON 证据——全量 OCR 转写(ocr.full_text)、版面布局(layout.regions)、语义(画面场景、意图、实体、关系)、视觉细节、以及不确定项列表。模型拿到这些 JSON,就像真的”看见”了图。

2. 关键认知:在 dsh 上 modlens 是插件,不是 skill

安装第一步就差点走偏。modlens 官方 INSTALL.md 写的是”把 skills/modlens 文件夹拷到你的 harness skill 目录”,但文档开头的 Step 0 明确警告:

如果你在 DeepSeek Harness (dsh) 里,停下来。dsh 上 modlens 不是 skill,是原生插件。只装 skill 文件夹,你会既没有 modlens_read_image 工具,也没有 (modlens vision) 模型条目。

判断依据很简单:存在 ~/.dsh/ 即 dsh 环境。安装只需一条命令:

1
npx -y @deepseek-ai/dsh plugin --profile web add @liustack/modlens@3.16.5

它会:把插件写进 web profile 的 package.json(依赖 + bundles)、跑 pnpm 安装、并处理 pnpm 11 的发布窗口限制(写入 minimumReleaseAgeExclude)。验证:

1
npx -y @deepseek-ai/dsh plugin --profile web list

装完需要重启 dsh,模型选择器里才会出现 (modlens vision) 条目。

3. 配置视觉引擎:Gemini + 代理

引擎配置存放在 ~/.modlens/config.json(所有 harness 共享),视觉引擎有 Gemini / OpenAI 兼容 / Antigravity CLI 等多条路径。推荐 Gemini(免费 key、无头环境可用、读取只要 5~10 秒)。

1
2
modlens config set gemini-api.apiKey <KEY>
modlens config set provider gemini-api

健康检查(不耗额度):

1
2
3
4
5
6
7
Node
[ok] v24.19.0 (minimum 22.19)
Providers
[ok] gemini-api: apiKey: file
Selected provider
gemini-api
reason: provider set in the config file

判断成功的标准就是 Selected provider 那两行:选中的 provider 必须是上面 Providers 列表里 [ok] 的那个。其他 provider 显示 [!!] 属正常,无需处理。

4. 踩坑实录(含金量最高的一段)

4.1 直连失败 → 走本地代理

首次读取报 UND_ERR_CONNECT_TIMEOUT。排查发现这台机器直连不了 Googlegoogle.comgenerativelanguage.googleapis.com 全部超时,而 GitHub / npm registry 正常——典型的网络环境限制。

解法:检测到系统已启用本地 Clash 代理 127.0.0.1:7897,配置给 modlens:

1
modlens config set proxy http://127.0.0.1:7897

顺带学到的排障技巧:确定”是不是代理问题”,先看套上代理后能否建立 TLS 隧道、能否 GET 通目标域名,再谈业务请求。

4.2 间歇性 500/503:服务端故障,重试即可

代理通了之后,读取又随机报 500 INTERNAL / 503 Unavailable。用脚本逐字节复现 modlens 的请求,发现同样的请求有时 200 有时 500,成功率大约 25%~33%,且多个 Gemini 模型(3.5/3.6/3.7-flash)都这样——这是上游服务端/代理出口的间歇性不稳定,不是请求格式或配置问题。策略就是”带退避的重试”。

4.3 429 配额:免费 key 的日常

免费 key 的日配额/限流触发 429 RESOURCE_EXHAUSTED / RATE_LIMIT_EXCEEDED关键认知:Gemini 的配额是按 key 独立计算的——一把 key 烧完,换一把全新 key 就是全新额度。这正是”多 Key 轮换”能解决问题的理论根基。

4.4 地区限制:轮换救不了的坑

某次突然全部读取报 400 "User location is not supported for the API use"。查代理出口 IP 是泰国(曼谷)——Gemini API 不支持泰国。这是最阴的坑:它按 IP 地域一刀切,换多少把 key 都没用,只能把代理节点切到支持地区(美/日/港/新/韩等)。教训:遇到全 key 同时失败的 400,先查出口地区,别急着怀疑 key。

5. 核心 DIY:多 Key 自动轮换

5.1 为什么需要

我查了 modlens 源码:每 provider 只有单 key,没有多 key / 轮换 / 429 重试。多把 key 只能”手动换”。于是自研了一套。

5.2 三件套结构

组件 作用
rotate.ps1 key 池管理:add / list / rotate / status
引擎补丁 打进 dist/main.js任何入口读到 429 自动换 key 重试
ml.ps1 命令行读取包装器:429 轮换 + 5xx 退避重试(双保险)
patch.ps1 modlens 升级后一键重打引擎补丁

Key 池存在 ~/.modlens/api-keys.json(与 config.json 同目录、同信任级别)。

5.3 引擎补丁原理

补丁目标函数是 executeGeminiApi(Gemini 读取的核心)。改前:读一次 key → 发一次请求 → 失败直接抛错(429 成死路)。改后for(;;) 循环,只有 429 才触发轮换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for (;;) {
// 用 activeKey 发请求
const response = await apiFetch(url, { headers: { "x-goog-api-key": activeKey } });
if (response.ok) { /* 解析,返回 —— 不变 */ }

if (response.status === 429 && rotationCount < 8) {
const pool = JSON.parse(fs.readFileSync(poolPath, "utf-8"));
const next = pool[(idx + 1) % pool.length]; // 循环取下一个
if (next && next !== activeKey) {
setConfigValue("gemini-api.apiKey", next); // 持久化:下次会话直接用新 key
activeKey = next;
rotationCount++;
continue; // 重发同一个请求
}
}
throw new Error(`Gemini API error ${response.status}: ...`);
}

设计要点(每条都有讲究):

决策 理由
只对 429 轮换 配额按 key 独立,换 key 有效;而 400/500/503 对所有 key 一视同仁,轮换白费
取模循环 最后一把用完绕回第一把
先持久化再重试 复用 modlens 自己的 setConfigValue,让新 key 在下次读取/重启后仍然生效
上限 + 去重保护 防止死循环;key 池全烧完时明确报错提醒补 key
请求体一行不动 只改 x-goog-api-key 头,绝不破坏 API 契约

5.4 为什么”改一个文件”就全覆盖?

这是最有价值的架构洞见之一。modlens 包里其实是两个组件:

1
2
3
@liustack/modlens
├── dsh/index.js ← 插件壳:注册工具、粘贴转路径——没有任何读图逻辑
└── dist/main.js ← CLI 引擎:真正的读图、调 API、解析 JSON

插件壳的 execute() 不做读图,而是 spawn 子进程去跑 dist/main.js(源码里 CLI_PATH = new URL('../dist/main.js', import.meta.url))。所以GUI 粘贴、modlens_read_image 工具、命令行,最终都汇入同一份引擎代码——补丁只需打一处。这也解释了为什么 modlens 在 dsh 上要装成插件而不是 skill:插件是给 dsh 的适配壳,引擎是 pnpm 装进来的同一个 CLI。

6. 升级是补丁的天敌(以及怎么优雅应对)

npm 把 node_modules 里的包当不可变副本:升级 = 拉新 tarball = 手工改动被冲掉。实测:modlens 从 3.16.5 升到 3.16.7,补丁果然被覆盖

应对方案就是 patch.ps1:它内置补丁的前后文本 hunk,检测到 [patch:rotate] 标记消失就自动重打,再跑 node --check 校验语法。幂等可重复执行:

1
2
powershell -ExecutionPolicy Bypass -File "$env:USERPROFILE\.modlens\patch.ps1"
# => patch applied and syntax-checked OK

如果未来版本重写了目标函数、hunk 匹配不上,脚本会明确报 “no match” 而不是静默改坏文件——此时再人工出新的补丁。顺手收获:补丁被冲后重打时,发现激活 key 已经自动从 [1] 轮换到 [2]——说明轮换系统在真实读取中已经实战生效过。

7. 发布与合规:别把功劳和 license 搞错

沉淀成开源仓库 https://github.com/retr67/modlens-key-rotation 前,做了三件事:

  1. 隐私清理:仓库内无任何 key、代理端口、机器路径(脚本全部用 $env:USERPROFILE 派生),全仓正则扫描零命中
  2. 标注衍生关系:补丁是 modlens 源码的衍生修改,而 modlens 是 MIT(Copyright © 2026 Leon Liu/liustack)——MIT 允许修改再分发,但必须保留原始版权声明,所以仓库里放了 LICENSE.modlens(modlens 原文)+ ACKNOWLEDGMENTS.md(清晰区分”哪些来自上游、哪些是我们自己的”,并引导用户给上游点 star)
  3. 分清许可:我们自己的脚本/文档用 MIT,补丁 hunk 走 modlens 的 MIT

8. 最终效果

场景 识图 429 自动轮换
当前对话
新开对话 / 换工作区 ✅(插件是 profile 级) ✅(轮换在插件引擎内)
重启 dsh
命令行 ml.ps1 ✅(脚本 + 引擎双保险)

日常使用完全无感:粘贴图片 → 插件读取 → 429 自动换 key 重试。读一次约 3~5 秒,返回的是一份完整可引用的结构化证据。

9. 一些可以带走的心得

  1. 改第三方包先想清楚”不可变”代价:node_modules 补丁天然脆弱,必须脚本化 + 幂等 + 语法自检
  2. 架构决定补丁面:壳 + 引擎的分离,让”改一处覆盖所有入口”成为可能——设计工具时值得借鉴
  3. 排障先分层:连不上→查代理;全 key 挂→查地区;偶发失败→查服务端;单 key 挂→查配额
  4. 配额是按 key 独立的:多 key 轮换是真能续命的方案,几把免费 key 就能撑很久
  5. 合规不是小事:衍生作品必须带原作者 LICENSE 声明,署名页把归属写清楚,皆大欢喜

环境记录:Windows / DeepSeek Harness (dsh) web profile / model deepseek-v4-flash / modlens 3.16.7 / 2026-08

在 Windows 上给 DeepSeek Harness 接入 OpenViking 记忆插件:一次「看似简单却踩了一路坑」的记录

记录一次从零安装、反复排查、最终定位并修复 OpenViking 本地向量库 bug 的全过程。

背景

我用的 DeepSeek Harness(以下简称 dsh)是一个 AI WebUI,希望在对话里具备「长期记忆」能力。于是我想接入 OpenViking——一个面向 AI Agent 的上下文/记忆存储服务端。

我的环境:

项目
操作系统 Windows
Python 3.14.7
OpenViking 0.4.14
dsh profile web
记忆插件 @openviking/dsh-memory-plugin
Embedding 本地 GGUF 模型(512 维)
VLM OpenCode Go(deepseek-v4-flash

表面上看,这只是「装一个插件」;实际做下来发现,它等于:

1
2
3
4
5
6
装 dsh 插件
+ 装 OpenViking 后端
+ 装 Python 虚拟环境
+ 下载本地向量模型
+ 配置模型 API
+ 修几个 bug

一、先搞清楚:插件和服务端的关系

OpenViking 是独立的后端服务,而 dsh 插件只是「让 dsh 自动使用这个后端的桥」。

组件 角色
OpenViking 记忆存储 / 向量搜索 / 模型调用的服务端
dsh-memory-plugin 客户端集成:自动捕获消息、提交会话、注入记忆、暴露 viking_* 工具

所以:

  • OpenViking 需要单独启动、单独配置
  • dsh 插件让 dsh 在后台自动调用它,不需要手动手动导数据

二、安装过程中踩的坑

坑 1:一开始装错了对象

我最初误以为是装 OpenCode 插件,结果装到了 OpenCode 配置里。后来确认目标是 dsh 插件,清理掉误建内容后重新来。

教训:先确认目标平台,再动手。

坑 2:dsh 插件没有发布到 npm

执行:

1
dsh plugin --profile web add @openviking/dsh-memory-plugin

返回 404。

原因:@openviking/dsh-memory-plugin 当时没有发布到 npm registry。

解决:改用 GitHub 源码安装,把插件放进 dsh 的 web profile 里。

坑 3:OpenCode Go 不支持 embedding

我的模型 API 只有 OpenCode Go 订阅,但它只提供 OpenAI 兼容的 chat/completions 接口,测试 /embeddings 返回 404。

结论:

  • OpenCode Go 可以当 VLM,用来做记忆提取、总结
  • 但它不能做向量搜索

解决:采用混合方案:

1
2
OpenCode Go -> VLM(总结 / 记忆提取)
本地 GGUF 小模型 -> 向量搜索(Embedding)

坑 4:本地 embedding 编译失败

安装 openviking[local-embed] 时,llama-cpp-python 在 Python 3.14 上构建失败。

原因:临时目录/沙箱权限问题。

解决:用完整权限重试后构建成功。

坑 5:模型下载慢 / HuggingFace 连不上

  • HuggingFace 直连卡在 0 字节
  • f16 模型约 45MB,又慢又断

解决:

  • 换成 hf-mirror.com
  • 改用更小的 q4 量化模型(约 15MB)
  • 写自动重试下载脚本

坑 6:记忆文件生成了,但搜索不到(核心问题)

这是最折磨人的一个。

现象:

  • OpenViking 服务正常
  • 记忆文件正常生成
  • ov find / viking_search 全部返回空

服务日志出现:

1
2
openviking.storage.viking_vector_index_backend - ERROR - Error reading existing record before partial update: Strings must be encoded before hashing
openviking.storage.collection_schemas - ERROR - Failed to write to vector database: Strings must be encoded before hashing

第一层原因:向量维度不一致

一开始配置的是 768 维 embedding,后来换成 512 维本地模型,导致向量库 schema 不匹配。

解决:重置向量库,让它按当前维度重建。

但重建后仍然搜不到,说明还有更深的问题。

第二层原因:真正的 bug——xxhash 编码问题

最终定位到 OpenViking 的这个文件:

1
openviking/storage/vectordb/utils/str_to_uint64.py

代码:

1
2
def str_to_uint64(input_string: str) -> int:
return xxhash.xxh64(input_string).intdigest()

xxhash 在 Python 3 中要求传入 bytes,不能直接传 str,所以报错:

1
Strings must be encoded before hashing

这导致:

  • 记忆 Markdown 文件正常写入 ✅
  • 但向量没有成功写进向量库 ❌
  • 所以记忆文件存在,却搜索不到

修复:

1
2
def str_to_uint64(input_string: str) -> int:
return xxhash.xxh64(input_string.encode("utf-8")).intdigest()

修复后记忆搜索恢复正常。

坑 7:移动目录后启动失败

把 OpenViking 从 D:\LoopingIsle 搬到 D:\OpenViking 后,启动报:

1
Existing collection embedding metadata does not match current configuration.

原因:向量库元数据还记录着旧路径。

解决:因为模型没变、向量维度没变,只是路径变了,所以在配置里加:

1
2
3
4
5
{
"embedding": {
"allow_metadata_override": true
}
}

坑 8:端口被占用导致重复启动失败

再启动一个 OpenViking 时:

1
Application startup failed. Exiting.

原因:1933 端口已经有一个实例在运行。

解决:启动脚本先检测端口,避免重复启动。

三、为什么这个 bug 很多人没遇到?

这个 bug 主要在以下组合下出现:

  • Windows
  • Python 3.14
  • 本地向量库(backend: local
  • 本地 GGUF embedding 模型

而大多数用户使用的是:

  • Linux + Docker
  • 云端 embedding API
  • 云端/托管向量数据库
  • 较旧的 Python 版本

所以这条「本地 + Windows + 新 Python」的路径测试覆盖较少。

另外这个 bug 是「半坏」的:

  • 服务能启动
  • 记忆文件能生成
  • 只有搜索时静默返回空

这种问题往往最难被发现。

四、排查经验总结

这次最大的收获是:看到「表面正常」时要学会怀疑更深一层。

排查思路:

  1. 先确认服务健康:/health
  2. 再确认记忆文件是否存在:ov ls viking://user/default/memories
  3. 再用命令行直接搜:ov find
  4. 最后一定去看服务日志——日志里的堆栈往往才是真正原因

这题如果没有看服务日志,可能永远定位不到 xxhash 这种「八竿子打不着」的地方。

五、最终状态

  • 插件已装在 dsh web profile
  • OpenViking 服务已从 D:\OpenViking 正常运行
  • 记忆写入、搜索都正常
  • LoopingIsle 项目目录已保持干净

相关补丁和说明已整理成一个仓库:

1
https://github.com/Retr67/openviking-xxhash-fix

六、给后来者的话

  1. OpenViking 是独立服务端,dsh 插件只是客户端桥,两者都要配好。
  2. 如果只用 LLM API,注意它不一定支持 embedding,可能需要本地模型。
  3. 本地向量库 + 新 Python + Windows 是容易踩雷的组合,建议多看服务日志。
  4. 遇到「文件生成了但搜不到」,多半是索引/向量写入层出了问题,而不是记忆本身没存。

有始有终,值得好评。

虽然后面有点赶,但感觉把想呈现的效果都呈现了。

就像看了一段也许真的存在的冒险。人物的变化和成长也悄然地推进,不突兀挺好。

原本是单人游戏,但我是跟朋友一起用合作模式通的关。

对于多人体验而言,如果不是多人游戏荒了,不建议把这游戏作为首选项。

各种解密和动作体验中规中矩,在2026年下,没有啥新鲜内容。

各种互动还算不错,动作有些僵硬。

剧情方面中等偏上水准。

值得一提的是,在剧情的最后,当主角处在一个特殊状态下时,一开始无法正确地前往目的地,当我以为附近有什么谜题时,突然脑子里意识到了一个新的可能性,当我把这个可能性告诉我朋友,并真的成功时,当时带给我的尤里卡时刻,还是非常震撼的,并且这个设计也十分符合剧情和玩法,让游戏的机制玩法解释了剧情,让剧情和体验更上了一层楼。甚至我有点怀疑,作者是因为这个时刻,才做了这么一款游戏。

准备工作

在开始之前,请确保你具备以下条件:

  • 一台 Linux 服务器:本文以 Ubuntu为例。

  • 一个公网 IP:确保你的服务器拥有独立的公网 IP,或已做好端口映射。

  • 开放防火墙端口:确保服务器防火墙和云服务商的安全组放行以下端口:

端口 协议 用途
9987 UDP 语音通信 (最核心)
30033 TCP 文件传输 (头像/图标)
10011 TCP 服务器远程管理 (ServerQuery)
41144 TCP 新版客户端查询端口

许可证说明:TeamSpeak 3 对非商业用途免费,内置 1个虚拟服务器 和 32个在线用户 的限制,对绝大多数私人团队来说完全够用。


开始安装

系统更新与创建用户

为了安全,我们强烈建议不要使用 root 账号运行 TS3 服务。

更新系统

sudo apt update && sudo apt upgrade -y

创建一个名为 teamspeak 的系统用户

sudo adduser teamspeak

切换到该用户

su - teamspeak

下载服务端软件

进入用户目录,并从官网下载最新版服务端(请以官网最新版本号为准):

cd /home/teamspeak

下载服务端压缩包 (以 3.13.7 版本为例)

wget https://files.teamspeak-services.com/releases/server/3.13.7/teamspeak3-server_linux_amd64-3.13.7.tar.bz2

解压

tar -xjvf teamspeak3-server_linux_amd64-*.tar.bz2

启动服务器

进入解压后的目录,接受许可协议并启动:

cd teamspeak3-server_linux_amd64

创建同意许可协议的文件

touch .ts3server_license_accepted

启动服务器

./ts3server_startscript.sh start


🚨 关键步骤:保存管理员令牌

首次启动时,终端日志中会显示两行极其重要的信息:

ServerAdmin privilege key created, please use it to gain serveradmin rights for your virtualserver.
token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

请立即复制并保存这个 token(令牌)。如果你关闭了终端,将无法找回,只能清空数据重新初始化。


设置开机自启 (Systemd)

为了确保服务器在重启后自动运行,我们可以将其注册为系统服务。

创建服务文件 (需要 root 权限):

sudo nano /etc/systemd/system/teamspeak3.service

粘贴以下配置 (注意将 /path/to 替换为你的实际绝对路径):

[Unit]
Description=TeamSpeak 3 Server
After=network.target
[Service]
User=teamspeak
Group=teamspeak
WorkingDirectory=/home/teamspeak/teamspeak3-server_linux_amd64
ExecStart=/home/teamspeak/teamspeak3-server_linux_amd64/ts3server
ExecStop=/home/teamspeak/teamspeak3-server_linux_amd64/ts3server_startscript.sh stop
ExecReload=/home/teamspeak/teamspeak3-server_linux_amd64/ts3server_startscript.sh restart
Restart=always
RestartSec=15
[Install]
WantedBy=multi-user.target

重载配置并启用

sudo systemctl daemon-reload
sudo systemctl enable teamspeak3
sudo systemctl start teamspeak3


连接与使用

下载客户端:从 TeamSpeak 官网 下载适用于你系统的客户端。

连接服务器:打开客户端,点击 Connections -> Connect,输入你的 服务器 IP 地址(端口默认 9987,可不填)。

获取管理员权限:首次进入频道时,客户端会弹出窗口要求输入 Token。粘贴你在第三步保存的令牌,即可获得管理员权限。


常见问题排查 (Troubleshooting)

问题现象 可能原因与解决方案
客户端连接不上 1. 检查 UDP 9987 端口是否放行,这是最常见的坑。
2. 检查云服务商安全组策略。
听不到声音/语音卡顿 检查服务器带宽是否跑满,或客户端 Codec 设置是否过高。
丢失了管理员 Token 停止服务端,删除 /home/teamspeak/teamspeak3-server_linux_amd64 下的 query_ip_whitelist.txt 和 query_ip_blacklist.txt,重启后控制台会生成新 Token。

搭建 TeamSpeak 音乐机器人 (teamspeak-music-bot)

有了 TeamSpeak 服务器之后,不妨再给它配上一个“点歌台”。teamspeak-music-bot 是一个功能强大的开源音乐机器人,链接,支持从网易云音乐、QQ音乐、酷狗音乐、哔哩哔哩等多个平台搜索并播放音乐。它最大的亮点在于提供了一个 YesPlayMusic 风格的 WebUI 控制面板,让你可以通过浏览器轻松管理播放,体验非常接近专业的音乐播放器。

从零搭建自托管加密 DNS:AdGuard Home 完整配置指南

如果你受够了运营商的 DNS 劫持,想在手机、电脑上全局去广告,又希望保护上网隐私,这篇文章就是为你准备的。


📖 前言

我花了几天时间,从一台云服务器开始,一步步搭建了自己的加密 DNS 服务。整个过程踩了不少坑:证书申请、端口配置、iOS 描述文件、安卓 DoT 连接失败……最终所有设备都成功跑了起来。

这篇文章就是完整的操作记录,希望能帮你少走弯路。


🎯 最终效果

  • 全屋去广告:所有连接到 AdGuard Home 的设备自动屏蔽广告
  • 加密 DNS:手机蜂窝网络、公共 Wi-Fi 下 DNS 查询全程加密
  • 跨设备支持:iPhone、安卓、Windows 全部配置完成
  • 自动续期:证书到期自动更新,无需人工干预

🧱 整体架构

1
2
3
4
5
6
7
8
9
10
11
12
13
互联网


┌─────────────────┐
│ 云服务器 │
│ (公网 IP) │
│ 运行 AdGuard Home│
│ 监听 443/853 端口│
└─────────────────┘
│ │
▼ ▼
iPhone 安卓手机 电脑
(DoH/TLS) (DoH) (DoH)

第一部分:准备工作

1.1 域名与服务器

  • 域名:我使用的是在 NameSilo 注册的域名
  • 服务器:一台有公网 IP 的云服务器(我是阿里云)
  • 系统:Ubuntu

1.2 域名解析

在 DNS 服务商控制台添加 A 记录:

记录类型 主机记录 记录值
A @ 你的服务器公网 IP
A www 你的服务器公网 IP

第二部分:安装 AdGuard Home

2.1 一键安装

1
curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -v

安装完成后,访问 http://你的服务器IP:3000 进行初始化设置。

2.2 设置用户名和密码

按照网页指引完成设置,记住你设置的管理员账号密码。


第三部分:申请 SSL 证书

3.1 为什么需要证书?

AdGuard Home 启用 DoH/DoT 加密必须使用 SSL/TLS 证书。我使用 Let’s Encrypt 的免费证书。

3.2 手动申请证书(首次)

先通过手动 DNS 验证方式申请:

1
sudo certbot certonly --manual --preferred-challenges=dns -d 域名

系统会提示你添加 TXT 记录:

1
_acme-challenge.域名 TXT "这里是一串验证值"

添加完成后等待生效,按回车继续。证书申请成功,一共有两份。

⚠️ 注意--manual 方式不会自动续期!我们后面会改成自动方式。

3.3 查看证书内容

将输出的内容(从 -----BEGIN CERTIFICATE----------END CERTIFICATE-----)复制下来。


第四部分:配置 AdGuard Home 加密

4.1 进入加密设置

登录 AdGuard Home 管理面板 → 设置加密

4.2 填写证书

配置项 填写内容
启用加密 ✅ 勾选
服务器名称 域名
证书 粘贴 fullchain.pem 的全部内容
私钥 粘贴 privkey.pem 的全部内容
HTTPS 端口 443(DoH 端口)
DNS-over-TLS 端口 853(DoT 端口)

💡 进阶做法:填完内容后,可以改为填写文件路径,方便自动续期时自动加载。

4.3 放行端口

服务器防火墙(如 ufw):

1
2
3
sudo ufw allow 443/tcp
sudo ufw allow 853/tcp
sudo ufw reload

云服务商安全组:在控制台添加入站规则,允许 TCP 443 和 853 端口。


第五部分:各设备端配置

5.1 iPhone / iPad(通过描述文件)

推荐方式:使用在线工具生成描述文件

  1. 访问 dns.notjakob.com
  2. 选择 DNS-over-HTTPS (DoH)
  3. DoH server URL 填写:https://域名/dns-query
  4. IPv4/IPv6 填写你喜欢的公共DNS
  5. 生成并下载 .mobileconfig 文件
  6. 通过 QQ/微信/邮件传到 iPhone,iPhone用自带的文件APP另存,并在文件APP里打开,进行安装

安装路径:设置 → 通用 → VPN 与设备管理 → 已下载的描述文件

5.2 安卓手机(推荐 DoH)

安卓系统内置的“私人 DNS”仅支持 DoT(853 端口),但国内网络环境可能干扰 853 端口。推荐使用 DoH

使用 Intra 应用(Google 官方开源):

  1. 在 Google Play 下载 Intra

5.3 Windows 电脑

方法一:系统网络设置(推荐)

  1. 设置 → 网络和 Internet → Wi-Fi/以太网
  2. 点击当前网络 → DNS 服务器分配 → 编辑 → 填写你的服务器公网IP

第一部分:云服务器配置(搭建 Moon 中转节点)

  1. 租一台有公网 IP 的云服务器(推荐阿里云,最便宜有公网IP就行,系统选 Ubuntu)。

  2. 开放服务器的UDP 9993端口。

  3. 连接服务器:登录服务器,依次执行以下命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    # 安装 ZeroTier

    curl -s https://install.zerotier.com | sudo bash

    # 进入 ZeroTier 配置目录

    cd /var/lib/zerotier-one

    # 生成 moon.json 配置文件

    sudo zerotier-idtool initmoon identity.public > moon.json

    # 编辑 moon.json,找到 "stableEndpoints": [] 这一行,改成你的服务器公网 IP

    sudo vi moon.json

    # 示例:"stableEndpoints": ["123.123.123.123/9993"]

    # 根据 moon.json 生成 .moon 签名文件

    sudo zerotier-idtool genmoon moon.json

    # 创建 moons.d 目录并移动文件

    sudo mkdir -p moons.d

    sudo mv *.moon moons.d/

    # 重启 ZeroTier 服务

    sudo systemctl restart zerotier-one
    # 记下服务器的 Node ID(例如 `12abcdef34`)
    
    sudo zerotier-cli status

第二部分:本地被控电脑配置

  1. 安装 ZeroTier 客户端(官网下载)。

  2. 加入你创建的虚拟网络

    1. 在 ZeroTier 官网创建 Network,获得 16 位 Network ID。
    2. 你的电脑上右键 ZeroTier 图标 → Join Network → 输入 Network ID。
  3. 添加 Moon 节点(让你的电脑优先通过你的服务器寻找朋友):

    1. 以管理员身份打开命令提示符,进入 ZeroTier 安装目录(默认 C:\Program Files (x86)\ZeroTier\One\)。
    2. 执行:zerotier-cli orbit <你的服务器NodeID> <你的服务器NodeID>(两次输入相同 ID)。
  4. 在 ZeroTier 官网授权:将你和朋友的设备都勾选允许加入网络。

  5. 安装 Parsec,登录账号,并确保你的电脑设置为 “主机”(Host) 模式(默认就是)。

  6. (可选)设置网络优先级,让 Parsec 优先走 ZeroTier 虚拟网卡。

    1. 按下键盘上的 Win + R 键,在弹出的“运行”对话框中输入 ncpa.cpl,然后点击“确定”。

    2. 在打开的“网络连接”窗口中,找到名为 ZeroTier One 的虚拟网卡。右键点击它,选择 “属性”

      1. 在弹出的属性窗口中:

            找到并双击 Internet 协议版本 4 (TCP/IPv4)。

            在新窗口中,点击下方的 “高级” 按钮。

    3. 修改接口跃点数

      1. 在“高级TCP/IP设置”窗口中,取消勾选自动跃点”。
      2. 在“接口跃点数”输入框中,手动输入一个很小的数字,比如 1
      3. 点击“确定”保存设置。
    4. 重启电脑

第三部分:你的朋友(控制端)

  1. 安装 ZeroTier 客户端
  2. 加入同一个虚拟网络:输入你提供的 Network ID
  3. 添加同一个 Moon 节点
    1. 同样以管理员身份打开命令提示符,进入 ZeroTier 安装目录。
    2. 执行:zerotier-cli orbit <你的服务器NodeID> <你的服务器NodeID>(和之前执行的命令完全一样)。
  4. 安装 Parsec,登录同一个账号(或互加好友)。
  5. (可选同上)设置网络优先级,让 Parsec 优先走 ZeroTier 虚拟网卡。
  6. 连接你的电脑:在 Parsec 界面中,你的电脑会出现在列表中,点击即可远程连接并开始联机。

本体+DLC,全程多人通关。

非常优秀高质量的一款游戏。

最值得称赞的是画风,有别于市面上常见的游戏,茶杯头的画风独树一帜,非常具有辨识度。在我个人看来,算是非常美观且生动的。

弹幕也能看出来是有设计过的,能够对玩家进行考验与互动,特别的格挡机制,也给游戏增添了别样的韵味。唯一的缺点是,有些初看是弹幕,但实际上只是场景特效,在初遇时会难以区分。

多人方面,互救带来的乐趣与节目效果还算不错。但除此之外,并没有更进一步的地方,有点遗憾。steam自带的远程同乐,并不算多好的选择,特别是在中国大陆境内。我个人推荐的方式是zeroTier + parsec,这种方式需要你租一个便宜的有公网IP的服务器,具体细节可通过AI查询。

剧情方面,整体符合画风与基调,中等偏上,无功无过,并不足以成为加分项。

好玩!

肉鸽带来的无尽可能,再加上各种各样规则碰撞在一起形成的化学反应,令每一把自然形成的卡组都有着不同程度的爽感。

无论是当区蠕动,还是爽局平推,都能带来不同程度的乐趣。

最好玩的还当属多人模式了,令杀戮尖塔的游戏体验上升了不仅一个台阶,多人的讨论与卡牌联动。爽局与区局的互相分享与承担。这些都令游戏的体验维度增加了,给人予丰富的游戏体验。

0%