<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>super-mortal RSS</title>
    <link>https://supermortal.cn</link>
    <description>super-mortal 技术博客：记录一个凡人程序员在 AI Agent、全栈开发与 DevOps 中的踩坑实录与开源实践，欢迎一起交流进步。</description>
    <language>zh-CN</language>
    <lastBuildDate>Sun, 27 Sep 2026 00:00:00 GMT</lastBuildDate>
    <atom:link href="https://supermortal.cn/rss.xml" rel="self" type="application/rss+xml" />
    <image>
      <url>https://supermortal.cn/favicon/favicon-32x32.png</url>
      <title>super-mortal RSS</title>
      <link>https://supermortal.cn</link>
    </image>
    <item>
      <title>现代化命令行方案：Windows Terminal + PowerShell 7</title>
      <link>https://supermortal.cn/blog/windows-terminal-modern-cli</link>
      <guid isPermaLink="true">https://supermortal.cn/blog/windows-terminal-modern-cli</guid>
      <pubDate>Sun, 27 Sep 2026 00:00:00 GMT</pubDate>
      <category>教程</category>
      <category>windows</category>
      <category>powershell</category>
      <category>终端</category>
      <description>Windows 侧命令行底座怎么搭：装 Windows Terminal 和 PowerShell 7，设置默认配置文件，再分享我习惯的终端外观数值，彻底告别 CMD。</description>
      <content:encoded><![CDATA[<blockquote>
<p><strong>一句话结论</strong>：Windows Terminal + PowerShell 7，把 Windows 侧的命令行底座一次搭好，从此告别蓝色老窗口。</p>
</blockquote>
<p>现在很多 AI 编程工具都依赖一个靠谱的终端环境，终端好不好用，直接决定你折腾的效率。以前 Windows 用户只能忍着 <code>cmd.exe</code> 或者那个老旧的 PowerShell 蓝色窗口：没有多标签、没有配置化、复制粘贴都不太顺手，一个字——丑。如今 <strong>Windows Terminal + PowerShell 7</strong> 已经是 Windows 开发的标准配置，装完你就知道差距在哪了。</p>
<h2>先分清两个东西：终端和 Shell</h2>
<p>动手之前先分清两个概念，不然后面容易绕晕：</p>
<ul>
<li><strong>终端（Terminal）</strong>：你看到的那扇"窗口"，负责展示和输入。</li>
<li><strong>Shell</strong>：窗口里跑的命令解释器，你敲的命令由它翻译执行。</li>
</ul>
<p>简单来说，终端是"饭店大厅"，Shell 是"后厨"。以前 Windows 是把大厅和后厨焊死的（CMD 窗口只能配 cmd.exe），现在终于能自由搭配了。Windows Terminal 就是微软出的现代化终端模拟器，开源、支持多标签，一个窗口里能开 CMD、PowerShell、WSL 多种环境，随便切。</p>
<p>| 组件 | 推荐选择 | 理由 |
|---|---|---|
| <strong>终端模拟器</strong> | <strong>Windows Terminal</strong> | 目前 Windows 上最好的终端入口，没有之一 |
| <strong>Shell（原生）</strong> | <strong>PowerShell 7 (Core)</strong> | 比系统自带的 PowerShell 5.1 更快、跨平台、功能更强 |</p>
<h2>安装</h2>
<h3>1. 装 Windows Terminal</h3>
<p>打开 Microsoft Store（商店），搜索 <code>terminal</code>，装 <strong>Windows Terminal</strong>：</p>
<p><img src="./01.png" alt="Microsoft Store 搜索 terminal 结果页：Windows Terminal 已安装"></p>
<h3>2. 装 PowerShell 7</h3>
<p>还是在商店，搜 <code>powershell</code>，装 <strong>PowerShell</strong>：</p>
<p><img src="./02.png" alt="Microsoft Store 搜索 powershell 结果页：PowerShell v7.6.6 可免费下载"></p>
<blockquote>
<p><strong>注意</strong>：认准<strong>黑色图标</strong>那个 PowerShell，蓝色图标的是系统自带的旧版 Windows PowerShell 5.1，别装错了。另外安装时如果开着代理，商店可能下载失败，先关掉代理再装。</p>
</blockquote>
<h2>配置</h2>
<h3>1. 固定到任务栏</h3>
<p>搜索"终端"，右键选择<strong>固定到任务栏</strong>。以后单击任务栏图标就能打开，不用每次翻开始菜单：</p>
<p><img src="./03.png" alt="Windows 搜索&#x22;终端&#x22;结果右键菜单：选择固定到任务栏"></p>
<h3>2. 进入设置</h3>
<p>打开终端，点窗口标题栏的下拉箭头，选<strong>设置</strong>（快捷键 <code>Ctrl+,</code>）：</p>
<p><img src="./04.png" alt="终端窗口下拉菜单：通过设置选项进入配置页（快捷键 Ctrl+,）"></p>
<h3>3. 设置默认启动</h3>
<p>在设置左侧点<strong>启动</strong>，改三个地方：</p>
<ul>
<li><strong>默认配置文件</strong>：选刚装的 <strong>PowerShell</strong>。不选的话，打开还是老的蓝色窗口，等于白装。</li>
<li><strong>启动大小</strong>：我喜欢 <strong>100 × 25</strong>（列 × 行），窗口不会太小挤得慌，也不会太大占满屏。</li>
<li><strong>在计算机启动时启动</strong>：默认是关的，想开机就自动开终端的打开它就行。</li>
</ul>
<p><img src="./05.png" alt="终端设置-启动页：默认配置文件下拉框、启动大小 100×25、开机自启开关"></p>
<h2>外观调教（我的习惯数值）</h2>
<p>进<strong>配置文件 → 默认值 → 外观</strong>，下面几个是我用下来比较顺手的数值，分享给你参考：</p>
<ul>
<li><strong>边框间距</strong>：12（四个方向都设 12，文字不会贴边）</li>
<li><strong>背景不透明度</strong>：89%（稍微透一点点，能隐约看到后面的窗口，但不影响阅读）</li>
<li><strong>字号</strong>：9</li>
<li><strong>行高</strong>：1.2</li>
<li><strong>单元格宽度</strong>：0.6</li>
</ul>
<p><img src="./06.png" alt="终端设置-默认值外观：边框间距 12、背景不透明度 89%"></p>
<p><img src="./07.png" alt="终端设置-默认值外观：Cascadia Mono 字体，字号 9、行高 1.2、单元格宽度 0.6"></p>
<blockquote>
<p><strong>小提示</strong>：这些数值是我自己用着舒服的参数，没有标准答案，按你的屏幕分辨率和喜好调就行。</p>
</blockquote>
<p>全部配置完，记得点右下角<strong>保存</strong>。</p>
<h2>使用</h2>
<p>之后从任务栏点开终端，默认就是 PowerShell 7，直接开干。日常写命令、跑脚本、切 WSL，都在这个窗口里搞定，不用再开一堆乱七八糟的黑色窗口。</p>
<h2>总结</h2>
<p>| 组件 | 用什么 |
|---|---|
| <strong>入口</strong> | Windows Terminal |
| <strong>日常操作</strong> | PowerShell 7 |</p>
<p>别再用老旧的 CMD 了。一个窗口装下 CMD、PowerShell、WSL，还能自定义外观——这套底座搭好，后面折腾 AI 编程工具、跑脚本都顺手很多。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Super Agent：我把「多智能体协同开发」打包成了 Skill，并开源了</title>
      <link>https://supermortal.cn/blog/super-agent</link>
      <guid isPermaLink="true">https://supermortal.cn/blog/super-agent</guid>
      <pubDate>Sun, 20 Sep 2026 00:00:00 GMT</pubDate>
      <category>教程</category>
      <category>ai</category>
      <category>开源项目</category>
      <category>skill</category>
      <description>把「主智能体只调度、子智能体各司其职」的协同开发流程封装成可加载的 Skill：文件即记忆、修 bug 靠 resume 同 ID。已开源并在腾讯 SkillHub 发布，TRAE 实测 11 小时跑完 14 个任务的项目</description>
      <content:encoded><![CDATA[<blockquote>
<p><strong>一句话结论</strong>：我把「多智能体协同开发」封装成了一个开箱即用的 AI Skill —— 加载后 AI 会按「计划 → 开发 → 测试 → 修正」的工程化流程干活，主智能体只调度不写码，修 bug 靠 resume 同 ID 保住上下文。已开源（MIT），也在腾讯 SkillHub 发布了。</p>
</blockquote>
<h2>为什么写这个东西</h2>
<p>先说动机。我用 AI 写代码的时间不算短，但每次把一个大项目丢给单个 agent 从头做到尾，总会撞上三个瓶颈：</p>
<p>| 瓶颈 | 具体表现 |
|---|---|
| <strong>上下文窗口爆炸</strong> | 对话一长，模型注意力就散了，连自己之前为什么这么写都记不住 |
| <strong>修 bug 失忆</strong> | 改 bug 的时候，agent 已经忘了当初的实现意图、忘了测试报告里的具体缺陷 |
| <strong>测试与开发互相污染</strong> | 写代码的 agent 自己测自己的代码，永远缺一个独立审查的视角 |</p>
<p>我试过手动拆任务、手动切对话，也能跑，但每次都要重复一套流程，太折腾。后来想明白了：与其每次手搓，不如把整套「角色分工 + 修正循环」固化成一份可加载的 Skill。于是就有了 <strong>Super Agent</strong>。</p>
<h2>它是什么</h2>
<p><strong>Super Agent</strong> 是一个 AI Agent Skill：加载之后，AI 会按「<strong>计划 → 开发 → 测试 → 修正</strong>」的工程化流程推进项目，而不是一口气把代码写完就交差。</p>
<p>简单来说，它给 AI 配了一个「<strong>项目经理 + 一支分工明确的团队</strong>」：</p>
<p>| 角色 | 剧本文件 | 职责 |
|---|---|---|
| <strong>主智能体</strong>（编排者） | <code>agents/orchestrator.md</code> | 拆任务、调度、写日志、判 PASS/FAIL。<strong>永不修改业务代码</strong>，就是个包工头 |
| <strong>计划智能体</strong> | <code>agents/planner.md</code> | 读需求文档，一次性生成 <code>dev-plan.md</code> 和 <code>dev-spec.md</code>，整个流程只跑一次 |
| <strong>开发智能体</strong> | <code>agents/developer.md</code> | 按单个任务写代码、记录变更、修 bug |
| <strong>测试智能体</strong> | <code>agents/tester.md</code> | 只读审查、写测试报告。<strong>也不修改业务代码</strong>，保证独立视角 |
| <strong>评估智能体</strong>（可选） | <code>agents/grader.md</code> | 回归评估，输出 <code>benchmark.json</code> |</p>
<p>五个角色都有独立的剧本文件，你可以按项目替换，但<strong>不能改</strong>主智能体的串行调度、同 ID resume 和上下文隔离规则。</p>
<h2>核心机制：角色分离 + 修正循环</h2>
<h3>修正循环：修 bug 不失忆的关键</h3>
<p>测试不通过时，不是重新开一个 agent，而是 <strong>resume 同一个 agent</strong>：</p>
<pre><code>开发 Agent（新 ID）→ 测试 Agent（新 ID）
 ↑                    │
 │                    │ FAIL
 └── resume 同一 dev ←┘
      （修完）
      ↓
      resume 同一 tester（重测）
</code></pre>
<p>为什么这么设计？因为<strong>上下文就是记忆</strong>。重开一个新 agent 等于让一个失忆的人来修 bug——它看不到当初写代码时的思路，只能瞎猜。resume 同 ID，才能保证「我当初为什么这么写」「测试报告里具体哪里不达标」这些信息还在脑子里。</p>
<blockquote>
<p><strong>注意</strong>：同一任务最多测 <strong>5 轮</strong>，第 5 轮仍 FAIL 就标记 ⚠️ 强制通过，不阻塞后续任务。防止死循环耽误总进度。</p>
</blockquote>
<h3>五条不可破坏的原则</h3>
<p>| 原则 | 含义 |
|---|---|
| <strong>文件即记忆</strong> | 所有产出（计划、代码、报告、经验）落盘到批次目录，不依赖对话上下文 |
| <strong>隔离即常态</strong> | 每个子智能体只看到主智能体传过去的内容，假设它什么业务背景都不知道 |
| <strong>记录即保险</strong> | 所有时间由 <code>scripts/now.py</code> 获取，禁止推算 |
| <strong>路径即接口</strong> | 主智能体只拿文件路径、不读内容；子智能体收到路径后自己读 |
| <strong>resume 同 ID</strong> | 修 bug resume 同开发 Agent，重测 resume 同测试 Agent，新任务开新 Agent |</p>
<h3>为什么必须严格串行</h3>
<p>这个 skill 里所有智能体调用必须<strong>严格串行</strong>，包括测试子代理之间。这不是性能取舍，而是保护 resume 机制和上下文隔离的设计前提。违反任何一条都算破坏这个 skill：</p>
<ol>
<li>主智能体修改业务代码 → 严格禁止</li>
<li>一个任务对应一个 dev Agent → 禁止复用同 ID dev 处理多个任务</li>
<li>下一个任务必须等当前任务完全完成（含修正循环）</li>
<li>测试子代理之间必须串行，不能多个 tester 同时跑</li>
<li>tester FAIL → 修 bug 必须 resume 同一 dev Agent</li>
<li>dev 修完 → 重测必须 resume 同一 tester Agent</li>
<li>跨 role 并行（如前后端同时跑）→ 不允许</li>
<li>更新 <code>dev-plan.md</code> 只能用 Edit 单行替换，禁止 Write 覆盖整个文件</li>
</ol>
<h2>真实跑通：在 TRAE 里开发 CLI Manager</h2>
<p>光说不练假把式，我直接用它在 TRAE 里跑了一个真实项目：<strong>CLI Manager</strong>（极轻量桌面 CLI 工具管理器，Tauri 2 + React 18 技术栈，识别·运行·升级一条龙）。需求文档和 UI 线框图丢给它，剩下的全看它自己表演。</p>
<h3>阶段 A：加载 skill，自动初始化</h3>
<p>加载 skill 后只给了一句话需求，它先检查环境（Rust 1.98.1 / Node v24.20.0），然后跑 <code>init_workspace.py</code> 创建独立批次目录，所有产出都会落在里面，不污染对话上下文：</p>
<p><img src="01.png" alt="TRAE 中 super-agent 阶段 A 初始化：主智能体检查环境（Rust 1.98.1 / Node v24.20.0）后执行 init_workspace.py 创建 .super-agent 批次目录，并写入 main-log.md 启动日志"></p>
<h3>阶段 B：planner 生成完整计划</h3>
<p>初始化完进入阶段 B，启动 planner 计划智能体。它读需求文档和线框图后，一次性产出 <code>dev-plan.md</code>（阶段任务表）、<code>dev-spec.md</code>（编码规范）、<code>requirements.md</code>（需求摘要）：</p>
<p><img src="02.png" alt="TRAE 中 super-agent 阶段 B 计划生成：planner 产出 dev-plan.md、dev-spec.md、requirements.md 共 6 个阶段、14 个任务（T0-T13，严格串行），首个任务 T0 为项目脚手架与 IPC 适配层"></p>
<h3>阶段 C：任务循环，逐个开发 + 独立测试</h3>
<p>计划就绪后进入任务循环，每个任务都是「新 dev → 新 tester → 判定 PASS/FAIL」。T0 脚手架任务里，开发智能体一口气创建了根配置 7 个、后端 7 个、Rust 源码 4 个、业务模块占位 7 个文件：</p>
<p><img src="03.png" alt="TRAE 中 super-agent 阶段 C 任务循环：T0 脚手架任务进行中，Sub Coding Agent 分步创建根配置、src-tauri 后端、Rust 源码与业务模块占位文件，左侧 .super-agent 目录实时生成"></p>
<h3>验收：双重验收 + 浏览器走查</h3>
<p>每个任务完成后不是它自己说了算，要过<strong>静态测试 + 浏览器走查</strong>双重验收。T0 完成后判定 PASS，才放行进入 T1 元数据模块：</p>
<p><img src="04.png" alt="TRAE 中 super-agent T0 双重验收 PASS：静态测试与浏览器走查均通过，主智能体标记任务完成并进入 T1 元数据模块，dev-plan.md 与 main-log.md 同步更新"></p>
<p>走查环节用的是浏览器自动化 Agent，8 项检查（页面加载、5 个 tab 切换、侧栏导航、纸张手绘风视觉、深浅主题切换、console 无报错、mock 数据可见）逐项验证并留存截图证据：</p>
<p><img src="05.png" alt="TRAE 中 super-agent 浏览器走查验收：Browser Use Agent 逐项执行 8 项检查并全部 PASS，覆盖页面加载、tab 切换、主题切换、console 报错收集与 mock 数据可见性，结果带证据截图"></p>
<h3>结果：14 个任务全部完成</h3>
<p>整个项目 5 个阶段、14 个任务全部跑完，<strong>11 份测试报告全部 PASS</strong>：</p>
<p><img src="06.png" alt="TRAE 中 super-agent 全部任务完成：5 个阶段 14 个任务（T0-T13）串行跑完，交付 NSIS 安装包 2.27 MB、Release 二进制 5.40 MB（LTO 裁剪达标）、Rust 测试 135 个全绿、前端构建零错误、测试报告 11 份全 PASS"></p>
<p>从晚上 9 点干到第二天早上 9 点半，<strong>任务耗时 11 小时 39 分</strong>——中间我完没全管，全程就是它自己循环。对比以前手动拆任务、手动催进度的日子，效率翻了好几倍。并且我验收的时候，工具上除了加载速度有点小问题是我当初没有考虑到的问题没有写入开发文档之外，其他的包括打包文件什么的是完全跑通并且实测可用了的</p>
<h2>怎么安装使用</h2>
<p>打开技能页 <strong><a href="https://skillhub.cn/skills/user_dd51ca54/super-agent">skillhub.cn/skills/user_dd51ca54/super-agent</a></strong>，复制页面上的安装指令（一句话），粘贴给你的 AI 工具——WorkBuddy、OpenClaw、QClaw 这类支持 SkillHub 的都行，它自己就会装好。WorkBuddy 用户更省事：直接在左侧技能库切到 SkillHub 来源，搜索 <code>super-agent</code>，点安装就行。</p>
<p><strong>依赖</strong>：Python >= 3.10（脚本只用标准库，无第三方依赖）。</p>
<p>加载后启动也很简单，一句话：</p>
<pre><code class="language-text">加载 super-agent skill，帮我开发 my-project 项目，需求文档在 /path/to/requirements.md
</code></pre>
<p>它会自动创建批次目录进入阶段循环。所有产出都落在 <code>&#x3C;你的项目>/.super-agent/runs/&#x3C;项目名-日期--批次号>/</code> 下：计划、测试报告、经验库、最终报告，全部可追溯。</p>
<blockquote>
<p><strong>小提示</strong>：如果没给需求文档，它会扫描已有批次，列出未完成的批次问你是继续还是新建——相当于自带断点续传。</p>
</blockquote>
<h2>适用 / 不适用</h2>
<p>| 场景 | 结论 |
|---|---|
| 长期、可追溯、需要独立审查的开发任务 | ✅ 适用 |
| 有明确需求文档、任务可拆分可独立验证的项目 | ✅ 适用 |
| 修 bug 时需要复用原 Agent 上下文的任务 | ✅ 适用 |
| 一次性小修改 | ❌ 杀鸡用牛刀，agent 直接改就行 |
| 强耦合、无法独立验证的任务 | ❌ 拆了反而更难 |
| 只是回答问题、解释概念 | ❌ 不需要多智能体协作 |</p>
<h2>写在最后</h2>
<p>这次把 super-agent 开源 + 发布 SkillHub，对我来说算是把「多智能体协作」从一次性实验变成了可复用资产。<strong>文件即记忆、隔离即常态、路径即接口</strong>这三条，对我自己写 Agent 工作流也很有启发。</p>
<ul>
<li>开源地址：<strong><a href="https://github.com/super-mortal/super-agent">github.com/super-mortal/super-agent</a></strong>（MIT，欢迎 Star ⭐）</li>
<li>SkillHub 发布页：<strong><a href="https://skillhub.cn/skills/user_dd51ca54/super-agent">skillhub.cn/skills/user_dd51ca54/super-agent</a></strong>（v0.0.2，AI 评分 4.6）</li>
</ul>
<p>如果你也在折腾多智能体，或者想让 AI 开发流程变得可追溯、可审查，欢迎拿去试试。</p>
]]></content:encoded>
    </item>
    <item>
      <title>DeepSeek Harness 蓝皮书：我开源了一套 dsh 实战指南</title>
      <link>https://supermortal.cn/blog/deepseek-harness-guide</link>
      <guid isPermaLink="true">https://supermortal.cn/blog/deepseek-harness-guide</guid>
      <pubDate>Sun, 06 Sep 2026 00:00:00 GMT</pubDate>
      <category>教程</category>
      <category>ai</category>
      <category>开源项目</category>
      <category>文档站</category>
      <description>从第一个能验收的 Agent 到可复用的工作系统，我开源了一套 E-INK 风格的 DeepSeek Harness (dsh) 实战指南，6 种语言，一切皆插件</description>
      <content:encoded><![CDATA[<blockquote>
<p><strong>一句话</strong>：从 0 到 1 搭一套可复用 Agent 工作系统的开源实战指南 —— E-INK 视觉、6 种语言、一切皆插件，命令可复制、报错可复现，照着做就能跑通。</p>
</blockquote>
<p>先交代下背景：我从 dsh 刚出来就在折腾 <a href="https://github.com/super-mortal/DeepSeekHarnessGuide">DeepSeek Harness</a>（简称 dsh），一路踩了不少坑。想找份"从入门到生产"的完整教程，结果翻了一圈官方仓库——只有 API 文档，没有一篇能照着跑的实战指南。行吧，没人写，那我写。于是就有了这份 <strong><a href="https://dsh.supermortal.top/">DeepSeek Harness 蓝皮书</a></strong>（<a href="https://github.com/super-mortal/DeepSeekHarnessGuide">GitHub 仓库</a>）。</p>
<p><img src="homepage.png" alt="DeepSeek Harness 蓝皮书（dsh 实战指南）网站首页 Hero 区：标题「DeepSeek Harness 蓝皮书」+ 副标题「以真实任务为主线的 dsh 实战指南」，配 deepdrink 鲸鱼品牌 Logo，标语「everything is a plugin · Agent = Model + Harness」，统计 34 篇（30 章 + 4 附录）、6 个 PART 分段、120 张配图、7.5 万字总字数、3 天入门，并支持中/英/日/韩/西/葡 6 种语言与 E-INK 纸感 × DeepSeek 墨蓝主题"></p>
<h2>这是什么</h2>
<p>简单来说，这是一份<strong>以真实任务为主线</strong>的 dsh 学习指南：从跑通第一个能验收的 Agent 开始，到搭出一套可复用的工作系统。它不是 API 文档的翻译——官方文档讲"是什么"，我讲"怎么跑通"。每一步的命令、截图、甚至报错，都是真实操作记录下来的，<strong>照着做就能复现</strong>。</p>
<p>视觉上我搞了一套 <strong>E-INK 纸感 × DeepSeek 墨蓝</strong> 主题：米白纸底、墨色文字、荧光黄标注，动效几乎为零，读起来更像一本纸书而不是网页。支持中/英/日/韩/西/葡 <strong>6 种语言</strong>，还内置了自研图片灯箱（点击放大、Ctrl+滚轮缩放、拖拽平移）。</p>
<h2>这份指南适合谁</h2>
<ul>
<li>刚接触 dsh，想从"装上能跑"走到"搭一套工作系统"的开发者</li>
<li>用过一阵 dsh，但对插件树、消息流、调度、MCP 还没串起来的进阶用户</li>
<li>想给团队沉淀一套 Agent 工作流模板、而不是每次从零写脚本的负责人</li>
</ul>
<h2>讲些什么</h2>
<p>全书按 PART 组织，从入门一路到生产环境：</p>
<ul>
<li><strong>PART 00-01 · 入门</strong>：为什么是 Agent 的乐高时代；认识 dsh、安装、Web UI、headless 模式、模型配置与排错</li>
<li><strong>PART 02-03 · 核心机制</strong>：插件树、核心子系统与消息流、会话日志；工具与沙箱、MCP、子代理、Skill、调度、本地部署</li>
<li><strong>PART 04 · 插件开发</strong>：dsh 的精髓"一切皆插件" —— 从安装插件到编写插件再到发布插件，defineTool、Hooks、UI 一条龙</li>
<li><strong>PART 05-06 · 实战与生产</strong>：个人网站、PPT、视频三个实战场景；部署、安全、可观测性与上下文管理</li>
<li><strong>附录</strong>：术语表、命令速查、学习路径、面试题</li>
</ul>
<p>每章遵循同一个模板：<strong>本章目标 → 动手 → 分节实操 → 这一章你学到了什么</strong>。实操章节还附赠"让 dsh 自己来：一句提示词"的玩法 —— 毕竟学 Agent 工具最好的方式，就是用 Agent 来干活。</p>
<h2>怎么读（按需挑）</h2>
<ul>
<li><strong>想跑起来</strong>：直接看 PART 00-01，跟着命令敲一遍，30 分钟内能跑通第一个 Agent</li>
<li><strong>想搞懂原理</strong>：从 PART 02 开始读，重点看插件树、消息流、调度三块</li>
<li><strong>想自己写插件</strong>：直奔 PART 04，把 defineTool / Hooks / UI 三件套跑通一遍就能上手</li>
<li><strong>要上生产</strong>：PART 05-06 看部署、安全、可观测性，附录有命令速查和面试题</li>
</ul>
<h2>写在最后</h2>
<p>如果你也在用 dsh，或者对 Agent 工作流感兴趣，直接在仓库按 PART 目录读就行。觉得有用的话，点个 Star 就是最大的鼓励 ⭐</p>
<ul>
<li>在线阅读：<strong><a href="https://dsh.supermortal.top/">dsh.supermortal.top</a></strong></li>
<li>仓库地址：<strong><a href="https://github.com/super-mortal/DeepSeekHarnessGuide">super-mortal/DeepSeekHarnessGuide</a></strong></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>VMware 虚拟机部署 fnOS 飞牛私有云</title>
      <link>https://supermortal.cn/blog/vmware-xu-ni-ji-bu-shu-fnos-fei-niu-si-you-yun</link>
      <guid isPermaLink="true">https://supermortal.cn/blog/vmware-xu-ni-ji-bu-shu-fnos-fei-niu-si-you-yun</guid>
      <pubDate>Sun, 21 Jun 2026 00:00:00 GMT</pubDate>
      <category>总结</category>
      <category>nas</category>
      <category>虚拟机</category>
      <description>本文讲在 Windows 电脑上安装 VMware 虚拟机，在 VMware 里部署飞牛系统</description>
      <content:encoded><![CDATA[<h2>一.下载所需文件</h2>
<h3>1. VMware Workstation Pro 下载</h3>
<ul>
<li><strong>官方下载地址</strong>：<a href="https://www.vmware.com/products/workstation-pro.html">VMware 官网产品页</a></li>
</ul>
<h3>2. fnOS 飞牛私有云镜像下载</h3>
<ul>
<li><strong>官网下载</strong>：<a href="https://www.fnnas.com">https://www.fnnas.com</a></li>
</ul>
<p>进官网找到最新版的 fnOS ISO 镜像下载。x86 设备选标准版就行，ARM 设备可以留意官网的 ARM 公测版</p>
<h2>二.检查并开启 CPU 硬件虚拟化</h2>
<h3>1 Windows 系统内查看虚拟化是否启用</h3>
<ol>
<li>右键底部任务栏 → 打开<strong>任务管理器</strong></li>
<li>顶部切换到【性能】→ 点击左侧【CPU】</li>
<li>右侧下拉找到<strong>虚拟化</strong>选项</li>
</ol>
<ul>
<li>显示【已启用】→ 系统层面没问题，跳过这步</li>
<li>显示【已禁用】→ 需要进 BIOS 开启，往下看</li>
</ul>
<h3>2 进 BIOS 开启 VT-x（虚拟化技术）</h3>
<p>虚拟化如果显示禁用，需要进电脑的 BIOS 把 VT-x 打开。不同品牌进 BIOS 的方式略有不同，这里整理了一下常见的按键</p>
<p>| 电脑品牌 / 主板类型   | 开机时按键             |
| --------------------- | ---------------------- |
| 华硕 ASUS             | F2（或 Del）           |
| 技嘉 GIGABYTE         | Del                    |
| 微星 MSI              | Del                    |
| 华擎 ASRock           | F2（或 Del）           |
| 联想 Lenovo（笔记本） | F2（或 Fn+F2）         |
| 联想 ThinkPad         | F1（或 Enter 再按 F1） |
| 戴尔 Dell             | F2                     |
| 惠普 HP               | F10                    |
| 宏碁 Acer             | F2                     |
| 小米 / Redmi 笔记本   | F2                     |
| 华为 MateBook         | F2（或 Fn+F2）         |</p>
<p><strong>操作步骤：</strong></p>
<ol>
<li><strong>重启电脑</strong>，在开机画面（品牌 Logo 出现时）连续按上面对应的按键</li>
<li>进入 BIOS 界面后，找到 <strong>Advanced</strong> 或 <strong>Configuration</strong> 菜单（中文界面叫"高级"或"配置"）</li>
<li>寻找以下选项之一（不同主板命名略有差异）：
<ul>
<li><strong>Intel Virtualization Technology</strong>（Intel VT-x）</li>
<li><strong>SVM Mode</strong>（AMD 平台的叫法）</li>
<li><strong>Virtualization Technology</strong></li>
<li><strong>VT-x</strong></li>
</ul>
</li>
<li>把它设为 <strong>Enabled</strong>（启用）</li>
<li>按 <strong>F10</strong> 保存并退出，选择 Yes 重启</li>
</ol>
<blockquote>
<p><strong>小提示</strong>：如果进 BIOS 后发现找不到这些选项，可能是 BIOS 被锁定了高级菜单。华硕等品牌需要在 BIOS 中按 F7 进入 Advanced Mode 才能看到完整选项。</p>
</blockquote>
<h3>3 关闭 Windows 自带 Hyper-V</h3>
<p>这一步很容易忽略。Windows 自带的虚拟机服务和 VMware 冲突，哪怕 BIOS 开了虚拟化，Hyper-V 不关的话依然会报错、网络不通、后台打不开。</p>
<ol>
<li>打开控制面板 → 程序 → 启用或关闭 Windows 功能</li>
<li>下拉列表，把以下三项<strong>全部取消勾选</strong>：
<ul>
<li>Hyper-V（全部子目录一起取消）</li>
<li>虚拟机平台</li>
<li>Windows 沙箱/盒</li>
</ul>
</li>
<li>点击确定，根据提示<strong>重启电脑</strong>生效</li>
</ol>
<p><strong>核心总结</strong>：虚拟化一共两层开关——BIOS 底层 + Windows 系统层。两层全部打开，VMware 才能正常运行，缺一不可</p>
<h2>三.安装飞牛和创建储存空间</h2>
<h3>1.安装飞牛</h3>
<ol>
<li>
<p>打开 VMware → 创建新的虚拟机 →选典型 (推荐) → 下一步</p>
</li>
<li>
<p>选择安装程序光盘镜像文件 (ISO) → 选择刚下的 fnos ISO → 下一步</p>
</li>
<li>
<p>客户机操作系统选 Linux → 版本选 Debian 12.x 64 位 → 下一步</p>
</li>
<li>
<p>给虚拟机取个名字，我的是 fnOS；位置：选一个非 C 盘、空间大的目录 → 下一步</p>
</li>
<li>
<p>最大磁盘大小：15-20G，这里直接选择15-20G就够了，用作安装系统的镜像，后续再扩展磁盘做硬盘储存</p>
</li>
<li>
<p>点自定义硬件（最关键的一步）在弹出的窗口里，改这 4 项</p>
</li>
</ol>
<ul>
<li>内存：改成 4096 MB（4GB），有 8G 内存就给 8192</li>
<li>处理器：2 核 或更多，勾选「虚拟化 Intel VT-x/EPT」</li>
<li>新CD/DVD(IDE)：确认是你那个 fnOS ISO，勾选启动时连接</li>
<li>网络适配器：选 NAT 模式（最简单，能上网）</li>
</ul>
<ol start="7">
<li>都弄好之后 → 点 关闭 → 再点完成，就可以看见回到了主页面</li>
</ol>
<p><img src="1.png" alt="VMware Workstation 中已创建好的 fnOS 虚拟机：内存 4GB、2 核 CPU、SCSI 硬盘 20GB、CD/DVD 挂载 fnOS ISO、网络适配器 NAT，配置文件位于 D:\always\Debian\fnOS.vmx，工作站版本 17.5.x，当前状态为已关机"></p>
<ol start="8">
<li>然后选中fnOS（自己填写的名字），点击开启此虚拟机</li>
</ol>
<p><img src="2.png" alt="fnOS 飞牛私有云 V1.1.26 安装向导第一步「选择安装位置」：识别到 [sda] VMware Virtual S 这块 20GB 虚拟 HDD、剩余 20GB 可用，点击下一步继续"></p>
<ol start="9">
<li>虚拟机开启后会看到如上页面，直接点击下一步，再点击下一步，然后会弹出一个关于格式化硬盘的说明直接点击确定即可，这里说的操作只针对 VMware 生成的虚拟磁盘文件，跟本机硬盘完全隔离，数据百分百安全，无需担心</li>
<li>启动完成后自动进网络设置，默认 DHCP（自动获取 IP 的意思）不需要修改 ，<strong>然后记录下面显示的 IP 地址</strong>即可→ 保存</li>
<li>等一会之后，浏览器打开第10步中记录的 IP 就能进入 fnOS 的 Web 后台初始化界面了</li>
</ol>
<p><img src="3.png" alt="浏览器打开 http://192.168.147.130:5666/system 看到的 fnOS「开始使用 fnOS」初始化表单：填写设备名称、超级管理员账号、密码、确认密码并勾选隐私协议与服务条款后点击「进入 fnOS」"></p>
<ol start="12">
<li>然后自己设置一个名称，账号以及密码就可以进去了，到了这里还需要给他扩展磁盘做存储用的磁盘</li>
</ol>
<h3>2. 扩展磁盘空间</h3>
<ol>
<li>关机这个虚拟机，再设置的地方找到硬盘选项，点击扩展硬盘，选择要扩展的大小，根据喜好即可</li>
<li>然后重新启动虚拟机，输入之前记录的 IP 进入飞牛的web页面，点击系统设置，点击储存空间管理，创建储存空间</li>
</ol>
<p><img src="4.png" alt="fnOS Web 控制台「系统设置 - 创建存储空间」向导第 1 步（共 4 步）：选择文件系统，对比 Btrfs（推荐，支持快照）、ZFS（支持快照/压缩/去重，适合企业）、ext4（不支持快照/压缩/数据校验，适合轻量文件服务），点击下一步继续"></p>
<ol start="3">
<li>选择系统推荐的然后点击下一步，然后选择刚刚扩展的那个硬盘，然后储存模型选择 Basic 和 Linear 都可以，然后一直下一步最后输入管理员密码进行插件即可</li>
<li>完成之后点击 Docker 进行首次启动，选择docker的数据目录就行了，就可以看见docker服务弄好了</li>
</ol>
<p><img src="5.png" alt="fnOS Docker 应用首次启动后的概览页：Docker 服务已开启、存储位置指向「存储空间 1 Basic」并启用开机自动启动，当前无容器、无项目、无镜像运行，下方实时显示 CPU、内存与网络使用率"></p>
<h2>四.开启 FN-Connect 远程访问</h2>
<blockquote>
<p>FN-Connect 是飞牛自研内网穿透服务，无需公网IP，凭FN ID加密远程访问NAS，开启之后就可以在任何设备上直接访问你这个电脑的虚拟机上安装的飞牛了，需要有飞牛账号，前往<a href="https://www.fnnas.com">飞牛官网</a> 进行免费注册就行了</p>
</blockquote>
<ol>
<li>在飞牛的 web 后台，点击系统设置，然后选择远程访问，点击开启 FN-Connect ，在弹出的登录框中直接登录的飞牛账号就行了，然后设置自己名字，这个名字是唯一的，如果其他用户有了会让你重新设置，好了之后，剩下的会全自动完成，最后就可以获得一个格式为 <strong>https://fnos.net/你设置的名字</strong>的远程访问地址了</li>
</ol>
<h2>五.常见问题</h2>
<p><strong>Q1：虚拟机硬盘格式化，会不会清空我电脑本地的资料？</strong></p>
<p>A：绝对不会。所有操作只针对 VMware 生成的虚拟磁盘文件，跟本机硬盘完全隔离，数据百分百安全。</p>
<p><strong>Q2：开机一直提示移除安装镜像，必须移除吗？</strong></p>
<p>A：不一定，虚拟硬盘已经装好系统了，光驱挂着的 ISO 不影响启动和使用。关机之后再去删除就行</p>
]]></content:encoded>
    </item>
    <item>
      <title>新项目推送到 GitHub 完整指南</title>
      <link>https://supermortal.cn/blog/xin-xian-mu-tui-song-dao-github-wan-zheng-zhi-nan</link>
      <guid isPermaLink="true">https://supermortal.cn/blog/xin-xian-mu-tui-song-dao-github-wan-zheng-zhi-nan</guid>
      <pubDate>Tue, 16 Jun 2026 00:00:00 GMT</pubDate>
      <category>总结</category>
      <category>github</category>
      <category>git</category>
      <description>从零开始的新项目推送到 Github 的完整流程总结</description>
      <content:encoded><![CDATA[<h2>一.绑定电脑</h2>
<pre><code class="language-Markdown"># 配置全局用户名（和你的 GitHub 用户名一致）
git config --global user.name "你的GitHub用户名"

# 配置全局邮箱（和你 GitHub 账号绑定的邮箱一致）
git config --global user.email "你的GitHub邮箱"
</code></pre>
<h2>二.关联电脑</h2>
<h3>第一步：打开终端（ CMD / PowerShell 都行）</h3>
<p>执行下面这条命令，<strong>一路回车，不要输入任何密码</strong></p>
<pre><code class="language-Bash">ssh-keygen -t ed25519 -C "你的GitHub注册邮箱"
</code></pre>
<h3>第二步：复制公钥内容</h3>
<pre><code class="language-Bash">type C:\Users\你的电脑用户名\.ssh\id_ed25519.pub
</code></pre>
<p>执行后，终端会输出一长串<strong>以 ssh-ed25519 开头、邮箱结尾</strong>的字符串，<strong>把它完整复制下来</strong></p>
<h3>第三步：把公钥粘贴到 GitHub</h3>
<ol>
<li>
<p>打开 GitHub 右上角头像 → <strong>Settings</strong></p>
</li>
<li>
<p>左侧找到 <strong>SSH and GPG keys</strong></p>
</li>
<li>
<p>点击 <strong>New SSH key</strong></p>
</li>
<li>
<p>Title 随便填（比如：我的笔记本）</p>
</li>
<li>
<p>Key type 选 <strong>Authentication key</strong></p>
</li>
<li>
<p>Key 框里<strong>粘贴你刚才复制的那串字符</strong></p>
</li>
<li>
<p>点 <strong>Add SSH key</strong> → 完成！</p>
</li>
</ol>
<h3>第四步：测试连接是否成功</h3>
<pre><code class="language-Markdown">ssh -T git@github.com
# 如果出现：Hi xxx! You've successfully authenticated...
# ✅ **说明 SSH 配置 100% 成功！**
</code></pre>
<h2>三.前提条件</h2>
<p>在开始之前，确保你已经：</p>
<h3>1.绑定电脑和关联电脑已经完成</h3>
<p>同一个电脑只需要一次，后续不用再重复操作，完成后会在<code>C:\Users\你电脑的用户名\.ssh</code>目录下有凭证</p>
<h3>2. 验证本地已安装 Git</h3>
<p>打开终端</p>
<pre><code class="language-Bash">git --version
</code></pre>
<h3>3. 已在 GitHub 上创建新仓库</h3>
<ul>
<li>
<p>登录 GitHub</p>
</li>
<li>
<p>点击右上角 "+" → "New repository"</p>
</li>
<li>
<p>填写仓库名称（如 <code>my-new-blog</code>）</p>
</li>
<li>
<p><strong>不要勾选</strong> "Add a README file"、"Add .gitignore" 等选项</p>
</li>
<li>
<p>点击 "Create repository"</p>
</li>
</ul>
<h2>四.标准推送流程</h2>
<h3>步骤 1：初始化 Git</h3>
<pre><code class="language-Markdown"># 项目根目录初始化 git 仓库（这会在文件夹里创建一个 .git 隐藏文件夹）
git init
</code></pre>
<h3>步骤 2：添加文件到暂存区</h3>
<pre><code class="language-Markdown"># 添加所有文件到暂存区（. 代表当前目录所有文件）
git add .

# 如果只想添加特定文件，比如只加 README.md：
git add README.md
</code></pre>
<h3>步骤 3：提交暂存区的文件</h3>
<pre><code class="language-Markdown"># 提交文件，-m 后面是提交说明（必须写，描述这次提交的内容）
git commit -m "initial commit: 初始化项目，添加基础代码"
</code></pre>
<h3>步骤 4：关联 GitHub 远程仓库</h3>
<p>回到 GitHub 的新仓库页面，复制仓库的 HTTPS/SSH 地址（推荐用 SSH），然后执行：</p>
<pre><code class="language-Markdown"># 关联远程仓库（替换成实际的仓库地址）

# HTTPS 方式：
git remote add origin https://github.com/你的用户名/仓库名.git

# SSH 方式（推荐）：
git remote add origin git@github.com:你的用户名/仓库名.git
</code></pre>
<h3>步骤 5：推送到 GitHub 仓库</h3>
<pre><code class="language-Markdown"># 把本地 master/main 分支推送到远程 origin 仓库，并设置默认上游分支

# 如果 GitHub 仓库默认分支是 main（直接推送）
git push -u origin main

# 如果 GitHub 仓库默认分支是 master（老项目）：
git push -u origin master

# 如果本地分支是 master，但想推送到远程 main：

# 重命名本地分支
git branch -m master main

# 推送并重命名远程分支
git push -u origin main
</code></pre>
<p><strong>说明：</strong></p>
<ul>
<li>
<p><code>-u</code> 参数用于绑定本地分支与远程分支，之后直接用 <code>git push</code> 就能自动推送</p>
</li>
<li>
<p>GitHub 新仓库默认分支是 <code>main</code>，老项目可能是 <code>master</code></p>
</li>
</ul>
<h2>五.Git 分支管理基础</h2>
<h3>1. GitHub 主分支的两种习惯</h3>
<ul>
<li>
<p><strong>早期 GitHub 默认主分支</strong>：<code>master</code></p>
</li>
<li>
<p><strong>现在 GitHub 新仓库默认主分支</strong>：<code>main</code></p>
</li>
<li>
<p><strong>你也可以在仓库设置里把任意分支设为默认主分支</strong></p>
</li>
</ul>
<h3>2. 本地分支与远程分支的对应关系</h3>
<h4>本地分支名 ≠ 远程分支名</h4>
<p>它们可以不一样，但<strong>强烈建议保持同名</strong>，避免混淆。</p>
<h4>推送时对应关系</h4>
<ul>
<li>
<p><strong>同名推送</strong>（最推荐）：本地分支名 = 远程分支名</p>
</li>
<li>
<p><strong>手动指定不同名</strong>：也可以手动指定不同名，但不建议，容易搞乱</p>
</li>
</ul>
<h3>3. 分支命名规范（行业通用）</h3>
<p>| 分支类型   | 命名示例          | 用途                                |
| ---------- | ----------------- | ----------------------------------- |
| 主分支     | <code>main</code> / <code>master</code> | 稳定可发布的代码                    |
| 开发分支   | <code>develop</code> / <code>dev</code> | 日常开发集成                        |
| 功能分支   | <code>feature/xxx</code>     | 新功能开发，如 <code>feature/user-login</code> |
| 修复分支   | <code>fix/xxx</code>         | 修复 bug，如 <code>fix/payment-error</code>    |
| 热修复分支 | <code>hotfix/xxx</code>      | 线上紧急修复                        |
| 发布分支   | <code>release/v1.2</code>    | 版本发布前的准备                    |</p>
<h3>4. 常用 Git 分支命令</h3>
<pre><code class="language-Bash"># 查看所有分支
git branch

# 查看当前分支
git branch

# 重命名当前分支
git branch -m 旧名 新名

# 切换分支
git checkout 分支名

# 创建新分支
git branch 新分支名

# 切换并创建新分支
git checkout -b 新分支名

# 推送新分支到远程
git push -u origin 新分支名

</code></pre>
<h2>六.快速参考</h2>
<h3>常用命令速查</h3>
<pre><code class="language-Bash"># 初始化仓库
git init

# 添加文件
git add .

# 提交
git commit -m "说明"

# 关联远程仓库
git remote add origin git@github.com:用户名/仓库名.git

# 推送
git push -u origin main

# 查看状态
git status

# 查看分支
git branch

# 查看远程仓库
git remote -v

</code></pre>
<h3>完整示例（从零开始）</h3>
<pre><code class="language-Bash"># 1. 进入项目目录

# 2. 初始化 Git
git init

# 3. 添加所有文件
git add .

# 4. 提交
git commit -m "initial commit: 初始化项目"

# 5. 关联远程仓库
git remote add origin git@github.com:super-mortal/my-new-blog.git

# 6. 推送到 GitHub（本地是 master）
git push -u origin master

# 或者推送到 main（本地 master → 远程 main）
git branch -m master main
git push -u origin main

</code></pre>
]]></content:encoded>
    </item>
    <item>
      <title>为博客添加 JSON-LD 结构化数据</title>
      <link>https://supermortal.cn/blog/wei-bo-ke-tian-jia-json-ld-jie-gou-hua-shu-ju</link>
      <guid isPermaLink="true">https://supermortal.cn/blog/wei-bo-ke-tian-jia-json-ld-jie-gou-hua-shu-ju</guid>
      <pubDate>Wed, 27 May 2026 00:00:00 GMT</pubDate>
      <category>教程</category>
      <category>建站</category>
      <category>seo</category>
      <description>记录我的博客本次 SEO 优化：为文章页与首页自动注入 BlogPosting / WebSite JSON-LD</description>
      <content:encoded><![CDATA[<blockquote>
<p><strong>TL;DR</strong>：本次 SEO 优化只做了一件事——给博客加上 JSON-LD 结构化数据。文章页自动输出 <code>BlogPosting</code>，首页自动输出 <code>WebSite</code>，以后发文无需手动维护</p>
</blockquote>
<h2>一.为什么要做这次优化</h2>
<p>博客基于 Astro + astro-theme-pure，原本已经具备较完整的 SEO 基础：页面 title / description、Open Graph、Twitter Card、canonical 链接、sitemap、RSS、robots.txt 等。</p>
<p>但全站缺少 <strong>JSON-LD 结构化数据</strong>（<code>application/ld+json</code>）。</p>
<p>JSON-LD 是一段嵌在页面里的 JSON，用 Schema.org 标准告诉搜索引擎：</p>
<ul>
<li>这是博客文章还是普通网页</li>
<li>标题、作者、发布时间分别是什么</li>
<li>站点名称和 URL 是什么</li>
</ul>
<p>对 Google 富摘要、AI 搜索引用都有帮助。更重要的是：只要在布局里配置一次，<strong>每篇新文章都会自动生成</strong>，不用在 Markdown 里重复写。</p>
<h2>二.我具体做了什么</h2>
<h3>1. 文章页：BlogPosting</h3>
<p>新建组件 <code>src/components/BlogPostingJsonLd.astro</code>，在 <code>src/layouts/BlogPost.astro</code> 中挂载。</p>
<p>组件会读取文章 frontmatter 里已有的字段，自动拼出 JSON-LD：</p>
<p>| JSON-LD 字段           | 数据来源                                  |
| ---------------------- | ----------------------------------------- |
| <code>headline</code>             | frontmatter <code>title</code>                       |
| <code>description</code>          | frontmatter <code>description</code>                 |
| <code>datePublished</code>        | <code>publishDate</code>                             |
| <code>dateModified</code>         | <code>updatedDate</code>（没有则用 <code>publishDate</code>）   |
| <code>author.name</code>          | 站点配置 <code>config.author</code>                  |
| <code>image</code>                | <code>heroImage</code> 或默认 <code>socialCard</code>           |
| <code>mainEntityOfPage.@id</code> | <code>https://supermortal.cn/blog/{文章 slug}</code> |</p>
<p>最终页面 <code>&#x3C;head></code> 中会出现类似：</p>
<pre><code class="language-html">&#x3C;script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    "headline": "文章标题",
    "author": { "@type": "Person", "name": "Mortal" },
    ...
  }
&#x3C;/script>
</code></pre>
<h3>2. 首页：WebSite</h3>
<p>新建组件 <code>src/components/WebSiteJsonLd.astro</code>，在 <code>src/pages/index.astro</code> 中挂载。</p>
<p>自动读取 <code>site.config.ts</code> 里的站点名、描述、作者、语言，输出：</p>
<pre><code class="language-html">&#x3C;script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "WebSite",
    "name": "super-mortal",
    "url": "https://supermortal.cn",
    ...
  }
&#x3C;/script>
</code></pre>
<h3>3. 布局改造：head 插槽</h3>
<p>JSON-LD 需要放在 <code>&#x3C;head></code> 里。为此改动了两个布局文件：</p>
<ul>
<li><code>src/layouts/BaseLayout.astro</code>：在 <code>&#x3C;head></code> 中增加 <code>&#x3C;slot name="head" /></code></li>
<li><code>src/layouts/ContentLayout.astro</code>：把 <code>head</code> 插槽转发给 BaseLayout</li>
</ul>
<p>文章页走 <code>BlogPost → ContentLayout → BaseLayout</code>，首页直接走 <code>BaseLayout</code>，两条路径都能把 JSON-LD 注入到 head</p>
<h2>三.附 Cloudflare近一个月流量统计</h2>
<p><img src="cloudflare%E7%BB%9F%E8%AE%A1.png" alt="Cloudflare 控制台 supermortal.cn 流量统计概览（4 月 27 日 - 5 月 27 日，近 30 天）：唯一访问者 1.13k、请求总数 14.73k，每天 UV 稳定在 20-90 之间、请求量在 100-2k 之间波动，作为博客接入 JSON-LD 结构化数据后搜索引擎收录效果与稳定访问量的佐证"></p>
]]></content:encoded>
    </item>
    <item>
      <title>服务器 Docker 和 SSL 证书清理</title>
      <link>https://supermortal.cn/blog/fu-wu-qi-docker-he-ssl-zheng-shu-qing-li</link>
      <guid isPermaLink="true">https://supermortal.cn/blog/fu-wu-qi-docker-he-ssl-zheng-shu-qing-li</guid>
      <pubDate>Mon, 04 May 2026 00:00:00 GMT</pubDate>
      <category>教程</category>
      <category>docker</category>
      <category>运维</category>
      <description>本教程适用于删除任意指定 Docker 项目（含容器、镜像）、对应 SSL 证书，以及重新无缓存构建容器，全程不影响服务器其他服务及项目</description>
      <content:encoded><![CDATA[<h2>一.删除指定 docker</h2>
<h3>1.查看所有容器</h3>
<pre><code class="language-bash">docker ps -a
</code></pre>
<blockquote>
<p>输出说明：可清晰看到每个容器的 CONTAINER ID（容器唯一ID）、IMAGE（所用镜像）、NAMES（容器名称），记录要删除的容器的名称或ID</p>
</blockquote>
<h3>2.执行容器删除操作</h3>
<h4>删除单个容器</h4>
<pre><code class="language-bash">docker rm -f -v 容器名或容器ID
</code></pre>
<h4>删除多个容器</h4>
<pre><code class="language-bash">docker rm -f -v 容器1名/ID 容器2名/ID 容器3名/ID
</code></pre>
<h4>命令解释</h4>
<ul>
<li>-f：强制停止并删除目标容器（即使容器处于运行中，也能直接删除）</li>
<li>-v：删除容器自带的内部存储、临时缓存及专属数据卷，确保容器相关缓存彻底清理</li>
<li>此操作仅删除 Docker 容器及内部关联缓存，<strong>不会删除服务器本地的项目文件</strong>（如挂载的代码目录）</li>
<li>仅删除指定容器，不影响服务器上其他正在运行或已停止的容器</li>
</ul>
<h3>3.挂载目录一起删除</h3>
<pre><code class="language-markdown"># 查看挂载路径

docker inspect 容器名 | grep -E 'Source|Destination'

# 输出举例

"Source": "/root/devhome/mysql-data",
"Destination": "/var/lib/mysql"
Source：你服务器本地的真实目录（数据库数据存在这里）
Destination：容器内部目录（不用管）

# 删除容器

docker rm -f -v 容器名/ID

# 删数据库数据

rm -rf 服务器本地Source路径
</code></pre>
<h2>二.删除对应镜像</h2>
<h3>1.查看所有镜像</h3>
<pre><code class="language-bash">docker images
</code></pre>
<blockquote>
<p>输出说明：通过第一步记录的容器所用镜像（IMAGE 字段），对应找到需要删除的镜像，记录镜像名或镜像ID</p>
</blockquote>
<h3>2.执行镜像删除操作</h3>
<h3>删除单个镜像</h3>
<pre><code class="language-bash">docker rmi 镜像名或镜像ID
</code></pre>
<h3>删除多个镜像</h3>
<pre><code class="language-bash">docker rmi 镜像1名/ID 镜像2名/ID 镜像3名/ID
</code></pre>
<h3>3.清理 Docker 无用残留缓存</h3>
<p>删除镜像后，执行以下命令，清理 Docker 系统中无用的残留缓存（如悬空镜像、废弃网络、临时构建产物等），不影响其他在用容器和镜像</p>
<pre><code class="language-bash">docker system prune -f
</code></pre>
<h3>4.注意事项</h3>
<ul>
<li>
<p>必须先删除容器，再删除镜像（若镜像仍被容器引用，直接删除镜像会失败）</p>
</li>
<li>
<p><code>docker system prune \-f</code> 仅清理“无人引用”的垃圾缓存，不会误删正在使用的镜像、容器或网络</p>
</li>
</ul>
<h2>三.删除目标项目绑定的 SSL 证书</h2>
<p>此步骤仅针对目标项目绑定的域名 SSL 证书（由 acme.sh 申请），彻底删除证书及相关配置，不影响其他域名的证书。</p>
<h3>1.查看所有 SSL 证书（定位目标证书）</h3>
<p>执行以下命令，查看服务器上所有由 acme.sh 申请的 SSL 证书，找到目标项目绑定的域名</p>
<pre><code class="language-bash">acme.sh --list
</code></pre>
<h3>2.注销 SSL 证书</h3>
<p>通知证书颁发机构（如 Let&#x26;#39;s Encrypt），该域名的证书不再使用，执行注销命令：</p>
<pre><code class="language-bash">acme.sh --revoke -d 目标域名
</code></pre>
<p>补充：若证书为 ECC 类型，需在命令后添加 --ecc，即：<code>acme\.sh \-\-revoke \-d 目标域名 \-\-ecc</code></p>
<h3>3.从 acme.sh 管理列表删除证书</h3>
<p>将目标域名的证书从 acme.sh 的管理列表中删除，避免后续自动续期：</p>
<pre><code class="language-bash">acme.sh --remove -d 目标域名
</code></pre>
<h3>4.彻底删除证书文件及缓存</h3>
<p>acme.sh 申请的证书文件默认存储在 ~/.acme.sh/ 目录下，执行以下命令删除对应域名的所有证书文件、私钥及缓存：</p>
<pre><code class="language-bash">rm -rf ~/.acme.sh/目标域名
rm -rf ~/.acme.sh/目标域名_ecc
</code></pre>
<h3>5.删除 Nginx 中对应域名的配置</h3>
<p>目标域名的反向代理、SSL 配置文件一般存储在 /usr/local/nginx/conf/ssl/ 目录下，执行以下命令删除</p>
<p><strong>具体路径说明可以查看往期文章:</strong> <a href="https://supermortal.cn/blog/li-jie-fu-wu-qi-zhong-de-nginx">理解服务器中的Nginx</a>)</p>
<h3>6.重载 Nginx 使配置生效</h3>
<pre><code class="language-bash">nginx -t  # 检查配置语法，输出 test is successful 即为正常
systemctl reload nginx  # 重载 Nginx
</code></pre>
<h2>四.重新构建容器时无缓存方法</h2>
<blockquote>
<p>此步骤适用于删除旧项目后重新构建，或更新项目代码后重新构建，全程不产生缓存，确保构建的容器为最新版本，避免缓存导致的旧代码残留。</p>
</blockquote>
<h3>1.先删除旧容器</h3>
<p>若重新构建的是原有项目，需先删除旧的容器（避免端口冲突、缓存残留）</p>
<pre><code class="language-bash">docker rm -f 旧容器名或ID
</code></pre>
<h3>2.进入项目目录</h3>
<p>切换到存放项目 Dockerfile 的目录（必须进入该目录，否则构建会失败）</p>
<pre><code class="language-bash">cd /服务器上的项目目录
</code></pre>
<h3>3.无缓存构建镜像</h3>
<p>执行无缓存构建命令，确保不使用任何旧缓存，完全基于最新代码构建镜像：</p>
<pre><code class="language-bash">docker build --no-cache -t 镜像名 .
</code></pre>
<p><strong>注意⚠️：</strong> --no-cache 是核心参数，用于禁用缓存；-t 用于指定镜像名称（自定义，与后续启动容器时的镜像名一致）；末尾的 . 表示当前目录（即 Dockerfile 所在目录）。</p>
<h3>4.启动新容器</h3>
<p>镜像构建完成后，执行启动命令，启动新容器（使用自己的启动参数，如端口映射、目录挂载等）</p>
<pre><code class="language-bash">docker compose up -d --build
</code></pre>
<h3>5.清理构建残留缓存</h3>
<p>构建完成后，执行以下命令，清理构建过程中产生的无用缓存（如中间构建层），节省磁盘空间</p>
<pre><code class="language-bash">docker image prune -f
</code></pre>
<h3>6.注意事项</h3>
<ul>
<li>
<p>--no-cache 必须添加，否则 Docker 会复用旧缓存，导致构建的镜像不是最新代码</p>
</li>
<li>
<p>启动容器时，确保端口、目录挂载等参数正确，避免与其他容器冲突</p>
</li>
<li>
<p>docker image prune -f 仅清理构建残留的无用缓存，不影响正在使用的镜像和容器</p>
</li>
<li>
<p>若构建失败，需先检查 Dockerfile 是否正确，再重新执行构建命令</p>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Astro框架中相对路径在URL拼接时的不确定性问题</title>
      <link>https://supermortal.cn/blog/Astro-kuang-jia-zhong-xiang-dui-lu-jing-zai-url-ping-jie-shi-de-bu-que-ding-xing-wen-ti</link>
      <guid isPermaLink="true">https://supermortal.cn/blog/Astro-kuang-jia-zhong-xiang-dui-lu-jing-zai-url-ping-jie-shi-de-bu-que-ding-xing-wen-ti</guid>
      <pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate>
      <category>知识点</category>
      <category>astro</category>
      <category>建站</category>
      <description>在一次偶然的爬虫抓取自己的博客数据时，遇到了social-card图片地址无法正确拼接为我的域名，始终为localhost导致社交卡片无法显示的问题</description>
      <content:encoded><![CDATA[<h2>一.问题发现</h2>
<p>偶然之间，我发现网络爬虫在抓取我的博客页面时，生成的 Open Graph (OG) 图片地址显示为：</p>
<pre><code class="language-markdown">https://localhost/images/social-card.png

# 而不是https://supermortal.cn/images/social-card.png，导致图片无法显示，下图为修复之后的截图
</code></pre>
<p><img src="./01.png" alt="书签管理工具的「创建书签」表单，爬虫正确抓取到 supermortal.cn 的 OG 元数据：网址 https://supermortal.cn、标题 Home、描述「super-mortal, 一个在数字世界中默默无闻的凡人」、图片网址 https://supermortal.cn/images/social-card.png，右侧带 social-card 缩略图"></p>
<h2>二.问题排查过程</h2>
<h3>第一步：检查配置文件</h3>
<p>首先，我检查了项目中的所有配置文件：</p>
<ol>
<li><strong><code>astro.config.ts</code></strong>（第 28 行）：</li>
</ol>
<pre><code class="language-typescript">site: 'https://supermortal.cn',
</code></pre>
<p>这里已经正确配置了正式域名</p>
<ol start="2">
<li><strong><code>src/site.config.ts</code></strong>（第 14 行）：</li>
</ol>
<pre><code class="language-typescript">socialCard: '/images/social-card.png',
</code></pre>
<p>这里使用的是相对路径</p>
<h3>第二步：分析代码逻辑</h3>
<p>在 <code>src/components/BaseHead.astro</code> 中，找到生成 meta 标签的关键代码（第 14 行）：</p>
<pre><code class="language-typescript">const socialImageURL = new URL(ogImage ? ogImage : config.socialCard, Astro.url).href
</code></pre>
<p>这里的问题是使用了 <code>new URL(..., Astro.url)</code> 方法。</p>
<h3>第三步：理解关键概念</h3>
<ol>
<li>我意识到这里涉及两个不同的 URL：</li>
</ol>
<ul>
<li><strong><code>astro.config.ts</code> 的 <code>site</code> 配置</strong>：定义网站在互联网上的正式域名（用于 RSS、sitemap 等元数据）</li>
<li><strong><code>Astro.url</code></strong>：在运行时由框架根据浏览器实际请求的 URL 提供的当前请求地址</li>
</ul>
<ol start="2">
<li>在开发环境中，浏览器请求 <code>http://localhost:3000/</code>，所以 <code>Astro.url</code> 就是 <code>http://localhost:3000</code>。</li>
<li>但是网络爬虫的工作环境可能与本地浏览器不同，在某些情况下，<code>Astro.url</code> 可能被错误识别为 <code>https://localhost</code></li>
</ol>
<h3>第四步：URL 拼接验证</h3>
<p>当使用 <code>new URL('/images/social-card.png', Astro.url)</code> 时：</p>
<ul>
<li>如果 <code>Astro.url</code> 是 <code>http://localhost</code>，结果就是 <code>http://localhost/images/social-card.png</code></li>
<li>如果 <code>Astro.url</code> 是 <code>https://supermortal.cn</code>，结果就是 <code>https://supermortal.cn/images/social-card.png</code></li>
</ul>
<h2>三.解决方案</h2>
<p><strong>使用绝对 URL</strong></p>
<p>直接在 <code>src/site.config.ts</code> 中使用完整的域名 URL：</p>
<pre><code class="language-typescript">socialCard: 'https://supermortal.cn/images/social-card.png'
</code></pre>
<blockquote>
<p><strong>修改之后可以看到爬虫正确抓取了我的博客信息，同时QQ分享链接时也正确显示了元数据信息</strong></p>
</blockquote>
<p><img src="./02.png" alt="QQ 聊天分享 supermortal.cn 主页与博客文章链接后，正确渲染出 Open Graph 预览卡片：主页显示「Home」标题与个人简介描述，文章链接显示「Astro框架中相对路径在URL拼接时的不确定性问题」标题与摘要、附缩略图，说明 social-card 改为绝对 URL 后 QQ 能正常抓取元数据"></p>
<blockquote>
<p><strong>未修改之前无法正确抓取只能显示如下图所示的纯链接</strong></p>
</blockquote>
<p><img src="./03.png" alt="QQ 聊天分享 supermortal.cn 主页与博客文章链接后，仅显示纯文本链接气泡 https://supermortal.cn 和 https://supermortal.cn/blog/astro-...，没有任何标题、描述、缩略图，正是修复前 social-card 使用相对路径被拼接成 localhost 时 QQ 无法抓取 OG 元数据的现象"></p>
<h2>四.问题根源总结</h2>
<ol>
<li><strong>配置分离的问题</strong>：<code>astro.config.ts</code> 的 <code>site</code> 配置和运行时的 <code>Astro.url</code> 是两个不同的概念</li>
<li><strong>URL 拼接的不确定性</strong>：<code>new URL(相对路径, Astro.url)</code> 依赖于 <code>Astro.url</code> 的实际值，不可控</li>
<li><strong>开发环境与爬虫环境的差异</strong>：本地开发和爬虫访问的 URL 环境可能不同</li>
</ol>
<h2>五.经验踩坑</h2>
<h3>踩到的坑 1：相对路径在某些环境下失效</h3>
<p>最初使用相对路径 <code>/images/social-card.png</code> 是因为：</p>
<ul>
<li>静态资源部署简单</li>
<li>不需要关心域名变化</li>
</ul>
<p>但实际上，在 URL 拼接时，相对路径仍然会依赖当前页面的 URL，这带来了不可预知的行为。</p>
<h3>踩到的坑 2：依赖 <code>Astro.url</code> 的不确定性</h3>
<p><code>Astro.url</code> 在不同运行环境下表现可能不同：</p>
<ul>
<li>开发环境：<code>http://localhost:3000</code></li>
<li>生产环境：<code>https://supermortal.cn</code></li>
<li>爬虫环境：可能不一样</li>
</ul>
<p>依赖这个值进行 URL 拼接是危险的</p>
]]></content:encoded>
    </item>
    <item>
      <title>云服务器手动部署OpenClaw指南</title>
      <link>https://supermortal.cn/blog/yun-fu-wu-qi-shou-dong-bu-shu-OpenClaw-zhi-nan</link>
      <guid isPermaLink="true">https://supermortal.cn/blog/yun-fu-wu-qi-shou-dong-bu-shu-OpenClaw-zhi-nan</guid>
      <pubDate>Wed, 25 Mar 2026 00:00:00 GMT</pubDate>
      <category>教程</category>
      <category>openclaw</category>
      <category>部署</category>
      <description>在非root用户下部署OpenClaw，大大提高安全性</description>
      <content:encoded><![CDATA[<blockquote>
<p><strong>写在前面：我之前手动部署的openclaw有两个问题。第一是在root用户下直接部署，一开始我是觉得这样最大权限，龙虾可以做的东西多一点，但是到了后来我发现很少情况需要用到root权限；第二是我允许通过公网访问，是因为我想浏览器也可以操作，但是实际运用起来发现，对接了通讯渠道之后再也用不到Web ui了，所以为了安全考虑，重新部署了我的”龙虾“，于是做了这个笔记，方便日后回顾和帮助同样想手动部署的人</strong></p>
</blockquote>
<h2>一.概念解释</h2>
<h3>1.什么是 Root 用户？</h3>
<p>root 是 Linux 系统的超级管理员，拥有最高权限。用 root 运行程序不安全，万一被攻击，攻击者就能控制整台服务器。</p>
<h3>2.为什么要用专用用户？</h3>
<p>把 OpenClaw 装在一个专门的"抽屉"里运行：</p>
<ul>
<li>OpenClaw 只能访问自己的文件</li>
<li>不会影响系统其他部分</li>
<li>更安全，即使被攻击也有限制</li>
</ul>
<h3>3.什么是 systemd</h3>
<p>systemd 是 Linux 系统的"自动管家"，可以：</p>
<ul>
<li>开机自动启动程序</li>
<li>程序崩溃自动重启</li>
<li>统一管理所有服务</li>
</ul>
<h2>二.登录 VPS</h2>
<p><strong>使用MobaXterm（Windows 推荐）</strong></p>
<ol>
<li>下载 <a href="https://mobaxterm.mobatek.net/">MobaXterm</a></li>
<li>点击左上角 <strong>Session</strong> → <strong>SSH</strong></li>
</ol>
<ul>
<li><strong>Remote host</strong>：你的 VPS IP地址</li>
</ul>
<ol start="4">
<li><strong>Specify username</strong>：填 <code>root</code></li>
<li>点击 <strong>OK</strong>，输入密码</li>
</ol>
<h2>三.确认工作</h2>
<h3>1.确认你的系统</h3>
<p>连接服务器后，执行：</p>
<pre><code class="language-markdown">cat /etc/os-release

# 应该看到 `Ubuntu` 或 `Debian` 字样
</code></pre>
<h3>2.确认你是 Root</h3>
<p>执行以下命令，输出必须是 <code>root</code></p>
<pre><code class="language-markdown">whoami
</code></pre>
<p>如果不是，执行下面命令输入密码就会切换到root</p>
<pre><code class="language-markdown">sudo -i
</code></pre>
<h2>四.创建专用用户</h2>
<h3>1.执行创建用户命令</h3>
<p><strong>在 root 用户下执行：</strong></p>
<pre><code class="language-markdown">useradd -r -s /usr/sbin/nologin -m -d /opt/openclaw openclaw
</code></pre>
<p>| 命令部分               | 含义           | 说明                              |
| ---------------------- | -------------- | --------------------------------- |
| <code>useradd</code>              | 创建新用户     | Linux 添加用户的命令              |
| <code>-r</code>                   | 系统账户       | 创建系统用户，不占用户编号        |
| <code>-s /usr/sbin/nologin</code> | 禁止登录       | 用户不能 SSH 登录，但程序可以运行 |
| <code>-m</code>                   | 创建 home 目录 | 自动创建用户的主目录              |
| <code>-d /opt/openclaw</code>     | 指定 home 目录 | 用户的主目录放在这里              |
| <code>openclaw</code>             | 用户名         | 给这个用户起个名字                |</p>
<h3>2.设置目录权限</h3>
<p><strong>在 root 用户下执行：</strong></p>
<pre><code class="language-markdown">chown -R openclaw:openclaw /opt/openclaw
</code></pre>
<p>| 命令部分            | 含义                         |
| ------------------- | ---------------------------- |
| <code>chown</code>             | 改变文件/目录的所有者        |
| <code>-R</code>                | 递归，即包含所有子目录和文件 |
| <code>openclaw:openclaw</code> | 用户名:用户组名              |
| <code>/opt/openclaw</code>     | 要修改权限的目录             |</p>
<p>意思是：把 <code>/opt/openclaw</code> 这个目录以及里面的所有东西都给 <code>openclaw</code> 用户。</p>
<h3>3.验证用户创建成功</h3>
<p><strong>在 root 用户下执行：</strong></p>
<pre><code class="language-markdown">id openclaw
</code></pre>
<p><strong>成功的输出类似这样：</strong></p>
<pre><code class="language-markdown">uid=998(openclaw) gid=998(openclaw) groups=998(openclaw)

# 能看到 uid、gid、groups 就说明用户创建成功了
</code></pre>
<h2>五.安装 Node.js</h2>
<h3>1.安装 Node.js 24</h3>
<p><strong>在 root 用户下执行：</strong></p>
<p><strong>第一步：安装 NodeSource 仓库</strong></p>
<pre><code class="language-markdown">curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
</code></pre>
<p>| 命令部分                                | 含义                        |
| --------------------------------------- | --------------------------- |
| <code>curl</code>                                  | 下载文件的命令              |
| <code>-fsSL</code>                                 | 安静模式，失败跟随重定向    |
| <code>https://deb.nodesource.com/setup_24.x</code> | NodeSource 官方安装脚本地址 |
| <code>sudo -E bash -</code>                        | 用 root 权限执行下载的脚本  |</p>
<p><strong>第二步：安装 Node.js</strong></p>
<pre><code class="language-markdown">apt-get install -y nodejs
</code></pre>
<p>| 命令部分          | 含义                 |
| ----------------- | -------------------- |
| <code>apt-get install</code> | apt 包管理器安装命令 |
| <code>-y</code>              | 自动确认，不再询问   |
| <code>nodejs</code>          | 要安装的包名         |</p>
<h3>2.验证安装成功</h3>
<p><strong>在 root 用户下执行：</strong></p>
<pre><code class="language-markdown">node --version
npm --version

# 成功的输出：

v24.0.0
10.9.0
</code></pre>
<h2>六.安装 OpenClaw</h2>
<h3>1.执行官方安装脚本</h3>
<p><strong>在 root 用户下执行：</strong></p>
<pre><code class="language-markdown">sudo -u openclaw bash -c 'curl -fsSL https://openclaw.ai/install.sh | bash'
</code></pre>
<p>| 命令部分                         | 含义                               |
| -------------------------------- | ---------------------------------- |
| <code>sudo -u openclaw</code>               | 以 openclaw 用户身份执行后面的命令 |
| <code>bash -c '...'</code>                  | 执行引号里的 bash 命令             |
| <code>curl -fsSL</code>                     | 下载文件                           |
| <code>https://openclaw.ai/install.sh</code> | OpenClaw 官方安装脚本              |
| <code>\| bash</code>                        | 把下载的内容作为脚本执行           |</p>
<h3>2.为什么要加 sudo -u openclaw？</h3>
<p>因为 OpenClaw 的配置文件和程序要装在 <code>/opt/openclaw</code> 目录里，这个目录属于 openclaw 用户。所以要用 openclaw 的身份来安装，这样所有文件都自动归 openclaw 用户。</p>
<h3>3.等待安装完成</h3>
<p>安装过程大约需要 3-5 分钟，看到程序开始运行安装向导就行，</p>
<p><a href="https://share.note.youdao.com/s/OtVTeFy8">向导选择可以点击查看教程 </a> Gateway bind部分保持默认就行</p>
<h3>4.找到 OpenClaw 安装位置</h3>
<p>安装脚本会自动把 OpenClaw 装到 openclaw 用户的目录下。找到它：</p>
<pre><code>find /opt/openclaw -name "openclaw" 2>/dev/null
</code></pre>
<p><strong>会输出类似：</strong></p>
<pre><code>/opt/openclaw/.npm-global/bin/openclaw
/opt/openclaw/.npm-global/lib/node_modules/openclaw
</code></pre>
<p>这就是 OpenClaw 命令的实际位置</p>
<h3>创建命令快捷方式</h3>
<p>因为实际路径很长，给它创建一个短名字的快捷方式：</p>
<pre><code>ln -s /opt/openclaw/.npm-global/bin/openclaw /usr/local/bin/openclaw
</code></pre>
<p>| 命令部分                                 | 含义                     |
| ---------------------------------------- | ------------------------ |
| <code>ln -s</code>                                  | 创建软链接（快捷方式）   |
| <code>/opt/openclaw/.npm-global/bin/openclaw</code> | 实际的 openclaw 程序位置 |
| <code>/usr/local/bin/openclaw</code>                | 快捷方式放在哪里         |</p>
<p><strong>⚠️ 为什么这一步必不可少</strong></p>
<p><strong>安装脚本把 OpenClaw 装到了 openclaw 用户的 npm 全局目录里：</strong></p>
<pre><code>/opt/openclaw/.npm-global/bin/openclaw
</code></pre>
<p>这个路径<strong>不在系统的 PATH 环境变量中</strong>，所以 shell 直接打 <code>openclaw</code> 会报 <code>command not found</code></p>
<p>在执行这一步之前，如果想运行 openclaw 命令，必须用<strong>完整路径</strong>：</p>
<pre><code>/opt/openclaw/.npm-global/bin/openclaw plugins install @tencent-connect/openclaw-qqbot@latest
</code></pre>
<p>创建快捷方式后，相当于在 <code>/usr/local/bin/openclaw</code> 放了一个指向真实程序的链接，而 <code>/usr/local/bin</code> 是在 PATH 里的，所以现在直接打 <code>openclaw</code> 就能找到命令了。</p>
<p><strong>本质就是</strong>：npm 把程序装到了一个不在 PATH 的自定义路径里，软链接这一步就是帮命令链接到系统 PATH 目录，让全系统都能直接调用。</p>
<h3>验证安装成功</h3>
<pre><code class="language-markdown">openclaw --version

# 显示版本号即为成功
</code></pre>
<h2>七.配置环境变量</h2>
<h3>1.什么是环境变量？</h3>
<p>环境变量就像"程序的环境设置"，告诉程序：</p>
<ul>
<li>配置文件在哪里</li>
<li>状态文件在哪里</li>
<li>工作目录在哪里</li>
</ul>
<h3>2.官方推荐的环境变量</h3>
<p>OpenClaw 官方文档推荐在非 root 部署时设置以下环境变量：</p>
<p>| 变量名                 | 作用                         | 我们的设置                              |
| ---------------------- | ---------------------------- | --------------------------------------- |
| <code>OPENCLAW_HOME</code>        | 程序的主目录，所有路径的基准 | <code>/opt/openclaw</code>                         |
| <code>OPENCLAW_STATE_DIR</code>   | 状态目录，存配置和会话       | <code>/opt/openclaw/.openclaw</code>               |
| <code>OPENCLAW_CONFIG_PATH</code> | 配置文件路径                 | <code>/opt/openclaw/.openclaw/openclaw.json</code> |
| <code>NODE_COMPILE_CACHE</code>   | Node.js 编译缓存，加速启动   | <code>/var/tmp/openclaw-compile-cache</code>       |
| <code>OPENCLAW_NO_RESPAWN</code>  | 避免重复启动开销             | <code>1</code>                                     |</p>
<h3>3.创建环境变量文件</h3>
<p><strong>在 root 用户下执行：</strong></p>
<p><strong>第一步：创建目录</strong></p>
<pre><code>sudo -u openclaw mkdir -p /opt/openclaw/.openclaw
</code></pre>
<p>| 命令部分                  | 含义                         |
| ------------------------- | ---------------------------- |
| <code>sudo -u openclaw</code>        | 以 openclaw 用户执行         |
| <code>mkdir -p</code>                | 创建目录（如果已存在不报错） |
| <code>/opt/openclaw/.openclaw</code> | 要创建的目录路径             |</p>
<p><strong>第二步：创建并编辑环境变量文件</strong></p>
<pre><code class="language-markdown">sudo -u openclaw nano /opt/openclaw/.openclaw/.env
</code></pre>
<p><strong>第三步：输入以下内容</strong></p>
<p>把下面 5 行内容复制粘贴进去，完事后Ctrl+O保存内容**（注意是字母O不是数字0）**，再按Enter确认保存，最后Ctrl+X退出编辑</p>
<pre><code class="language-markdown">OPENCLAW_HOME=/opt/openclaw
OPENCLAW_STATE_DIR=/opt/openclaw/.openclaw
OPENCLAW_CONFIG_PATH=/opt/openclaw/.openclaw/openclaw.json
NODE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache
OPENCLAW_NO_RESPAWN=1
</code></pre>
<h3>4.设置缓存目录权限</h3>
<pre><code class="language-markdown">mkdir -p /var/tmp/openclaw-compile-cache
chown openclaw:openclaw /var/tmp/openclaw-compile-cache
</code></pre>
<h2>八.接入QQ机器人</h2>
<blockquote>
<p>前往<a href="https://q.qq.com/qqbot/openclaw/index.html">QQ开放平台（openclaw）</a>注册并创建自己的机器人</p>
</blockquote>
<h3>1.安装插件</h3>
<pre><code class="language-bash">sudo -u openclaw bash -c "cd /opt/openclaw &#x26;&#x26; openclaw plugins install @tencent-connect/openclaw-qqbot@latest"
</code></pre>
<p>| 命令部分                                 | 含义                                             |
| ---------------------------------------- | ------------------------------------------------ |
| <code>sudo -u openclaw</code>                       | 以 openclaw 用户身份执行后面的命令               |
| <code>bash -c "..."</code>                          | 打开一个新的 bash shell，执行引号里的命令        |
| <code>cd /opt/openclaw</code>                       | 切换到 openclaw 安装目录                         |
| <code>openclaw plugins install</code>               | openclaw 的插件安装命令                          |
| <code>@tencent-connect/openclaw-qqbot@latest</code> | QQ Bot 插件的 npm 包名，@latest 表示安装最新版本 |</p>
<h3>2.添加 QQ 机器人账号</h3>
<pre><code class="language-bash">sudo -u openclaw bash -c "cd /opt/openclaw &#x26;&#x26; openclaw channels add --channel qqbot --token '你的AppID:你的AppSecret'"
</code></pre>
<p>| 命令部分                         | 含义                                                  |
| -------------------------------- | ----------------------------------------------------- |
| <code>sudo -u openclaw bash -c "..."</code> | 同上，以 openclaw 用户执行命令                        |
| <code>cd /opt/openclaw</code>               | 切换到 openclaw 目录                                  |
| <code>openclaw channels add</code>          | 添加频道账号的命令                                    |
| <code>--channel qqbot</code>                | 指定频道类型为 qqbot                                  |
| <code>--token 'AppID:AppSecret'</code>      | QQ 机器人的凭证，格式是 <code>AppID:AppSecret</code>，用冒号分隔 |</p>
<h3>3.重启服务</h3>
<pre><code class="language-bash">sudo systemctl restart openclaw-gateway
</code></pre>
<p>| 命令部分           | 含义                                               |
| ------------------ | -------------------------------------------------- |
| <code>sudo</code>             | 以 root 身份执行（systemd 服务管理需要 root 权限） |
| <code>systemctl</code>        | systemd 服务管理工具                               |
| <code>restart</code>          | 重启服务                                           |
| <code>openclaw-gateway</code> | openclaw 网关服务的名称                            |</p>
<h3>4.验证网关状态</h3>
<pre><code class="language-markdown">ss -tlnp | grep 18789

# 如果显示 LISTEN，说明正在运行，即成功
</code></pre>
<h2>九.创建开机自启服务</h2>
<h3>1.第一步：检测你的环境支持哪种方式</h3>
<p><strong>在 root 用户下执行：</strong></p>
<pre><code class="language-bash">systemctl list-units --type=service --state=running 2>/dev/null | head -5
</code></pre>
<ul>
<li><strong>如果有输出</strong>（列出了一些服务名）→ systemd 可用，用 <strong>方案 A</strong></li>
<li><strong>如果报错或无输出</strong>（如 <code>Failed to connect to bus</code>）→ systemd 不可用，用 <strong>方案 B</strong></li>
</ul>
<h3>方案 A：systemd 服务（推荐，适合完整 Linux 系统）</h3>
<h4>1.什么是 systemd 服务？</h4>
<p>systemd 是 Linux 的"服务管理器"。创建服务后，OpenClaw 可以：</p>
<ul>
<li>开机自动启动</li>
<li>崩溃后自动重启</li>
<li>不需要人工干预</li>
</ul>
<h4>2.创建服务配置文件</h4>
<p><strong>在 root 用户下执行：</strong></p>
<pre><code class="language-markdown">nano /etc/systemd/system/openclaw-gateway.service
</code></pre>
<h4>3.粘贴以下内容</h4>
<p>把下面的内容完整复制，粘贴进去</p>
<pre><code>[Unit]
Description=OpenClaw Gateway
Documentation=https://docs.openclaw.ai
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=openclaw
Group=openclaw
WorkingDirectory=/opt/openclaw
ExecStart=/opt/openclaw/.npm-global/bin/openclaw gateway --port 18789
Restart=always
RestartSec=5
TimeoutStartSec=90

Environment="OPENCLAW_HOME=/opt/openclaw"
Environment="OPENCLAW_STATE_DIR=/opt/openclaw/.openclaw"
Environment="OPENCLAW_CONFIG_PATH=/opt/openclaw/.openclaw/openclaw.json"
Environment="NODE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache"
Environment="OPENCLAW_NO_RESPAWN=1"

NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=read-only
PrivateTmp=true
ReadWritePaths=/opt/openclaw

[Install]
WantedBy=multi-user.target
</code></pre>
<p><strong>配置解释</strong></p>
<p>| 配置项                           | 含义                       |
| -------------------------------- | -------------------------- |
| <code>Description</code>                    | 服务的描述名称             |
| <code>After=network-online.target</code>    | 等网络准备好再启动         |
| <code>User=openclaw</code>                  | 以 openclaw 用户身份运行   |
| <code>Group=openclaw</code>                 | 以 openclaw 用户组身份运行 |
| <code>WorkingDirectory=/opt/openclaw</code> | 工作目录                   |
| <code>ExecStart=...</code>                  | 启动时执行的命令           |
| <code>Restart=always</code>                 | 崩溃后自动重启             |
| <code>RestartSec=5</code>                   | 崩溃后等 5 秒再重启        |
| <code>TimeoutStartSec=90</code>             | 启动超时 90 秒             |
| <code>Environment="..."</code>              | 环境变量设置               |
| <code>NoNewPrivileges=true</code>           | 不提升权限（安全）         |
| <code>ProtectSystem=strict</code>           | 限制访问系统目录（安全）   |
| <code>ProtectHome=read-only</code>          | 禁止访问 home 目录（安全） |
| <code>PrivateTmp=true</code>                | 私有临时目录（安全）       |
| <code>ReadWritePaths=/opt/openclaw</code>   | 允许读写的目录             |
| <code>WantedBy=multi-user.target</code>     | 开机在多用户模式下启动     |</p>
<h4>4.重新加载 systemd</h4>
<p><strong>在 root 用户下执行：</strong></p>
<pre><code class="language-markdown">systemctl daemon-reload
</code></pre>
<h4>5.设置开机自启</h4>
<p><strong>在 root 用户下执行：</strong></p>
<pre><code class="language-markdown">systemctl enable openclaw-gateway.service
</code></pre>
<h4>6.立即启动服务</h4>
<p><strong>在 root 用户下执行：</strong></p>
<pre><code class="language-markdown">systemctl start openclaw-gateway.service
</code></pre>
<h4>7.验证是否成功</h4>
<p><strong>在 root 用户下执行：</strong></p>
<pre><code class="language-markdown">systemctl status openclaw-gateway.service

# 成功的标志：

- 看到 `Active: active (running)` ✅
- 不是 `inactive` 或 `failed`
</code></pre>
<h3>方案 B：Crontab 开机任务</h3>
<h4>1.编写启动脚本</h4>
<p><strong>在 root 用户下执行：</strong></p>
<pre><code class="language-markdown">sudo -u openclaw tee /opt/openclaw/start-gateway.sh > /dev/null &#x3C;&#x3C;'EOF'
#!/bin/bash
source /opt/openclaw/.openclaw/.env
exec /opt/openclaw/.npm-global/bin/openclaw gateway --port 18789
EOF
chmod +x /opt/openclaw/start-gateway.sh
chown openclaw:openclaw /opt/openclaw/start-gateway.sh
</code></pre>
<h4>2.添加开机任务</h4>
<p><strong>在 root 用户下执行：</strong></p>
<pre><code class="language-markdown">crontab -e

# 如果是第一次用 crontab，选 `1` 用 nano 编辑
</code></pre>
<p><strong>在文件最后添加这一行</strong></p>
<pre><code>@reboot /opt/openclaw/start-gateway.sh >> /var/log/openclaw-gateway.log 2>&#x26;1
</code></pre>
<p>按 <strong>Ctrl + O</strong>，回车保存，<strong>Ctrl + X</strong> 退出</p>
<h4>3.立即启动 Gateway</h4>
<p><strong>在 root 用户下执行：</strong></p>
<pre><code>/opt/openclaw/start-gateway.sh &#x26;
</code></pre>
<h4>4.查看是否启动成功</h4>
<pre><code>sleep 3 &#x26;&#x26; sudo -u openclaw bash -c 'source /opt/openclaw/.openclaw/.env &#x26;&#x26; openclaw gateway status'
</code></pre>
<p>看到 <code>Listening: 127.0.0.1:18789</code> 说明成功。</p>
<h2>十.常用命令速查</h2>
<p>| 操作         | 方案 A（systemd）                                                                           | 方案 B（crontab）                                                                           |
| ------------ | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| 查看状态     | <code>systemctl status openclaw-gateway.service</code>                                                 | <code>sudo -u openclaw bash -c 'source /opt/openclaw/.openclaw/.env &#x26;&#x26; openclaw gateway status'</code> |
| 启动         | <code>systemctl start openclaw-gateway.service</code>                                                  | <code>/opt/openclaw/start-gateway.sh &#x26;</code>                                                          |
| 停止         | <code>systemctl stop openclaw-gateway.service</code>                                                   | <code>pkill -f "openclaw gateway"</code>                                                               |
| 重启         | <code>systemctl restart openclaw-gateway.service</code>                                                | <code>pkill -f "openclaw gateway" &#x26;&#x26; /opt/openclaw/start-gateway.sh &#x26;</code>                           |
| 查看日志     | <code>journalctl -u openclaw-gateway.service -n 50</code>                                              | <code>tail -50 /var/log/openclaw-gateway.log</code>                                                    |
| 实时日志     | <code>journalctl -u openclaw-gateway.service -f</code>                                                 | <code>tail -f /var/log/openclaw-gateway.log</code>                                                     |
| Gateway 状态 | <code>sudo -u openclaw bash -c 'source /opt/openclaw/.openclaw/.env &#x26;&#x26; openclaw gateway status'</code> | 同左                                                                                        |
| 健康检查     | <code>sudo -u openclaw bash -c 'source /opt/openclaw/.openclaw/.env &#x26;&#x26; openclaw health'</code>         | 同左                                                                                        |
| 查看版本     | <code>openclaw --version</code>                                                                        | 同左                                                                                        |
| 初始化向导   | <code>sudo -u openclaw bash -c 'source /opt/openclaw/.openclaw/.env &#x26;&#x26; openclaw onboard'</code>        | 同左                                                                                        |
| 创建备份     | <code>sudo -u openclaw bash -c 'source /opt/openclaw/.openclaw/.env &#x26;&#x26; openclaw backup create'</code>  | 同左                                                                                        |
| 健康诊断     | <code>sudo -u openclaw bash -c 'source /opt/openclaw/.openclaw/.env &#x26;&#x26; openclaw doctor'</code>         | 同左                                                                                        |</p>
<h2>十一.常见问题</h2>
<h3>Q1: 安装脚本报 <code>command not found</code></h3>
<p>说明 OpenClaw 安装到了非标准路径。找到它：</p>
<pre><code class="language-markdown">find /opt/openclaw -name "openclaw" 2>/dev/null
</code></pre>
<p>然后创建快捷方式：</p>
<pre><code class="language-markdown">ln -s /opt/openclaw/.npm-global/bin/openclaw /usr/local/bin/openclaw
</code></pre>
<h3>Q2: 服务启动失败？</h3>
<p><strong>先查看 Gateway 状态：</strong></p>
<pre><code class="language-markdown">sudo -u openclaw bash -c 'source /opt/openclaw/.openclaw/.env &#x26;&#x26; openclaw gateway status'
</code></pre>
<p><strong>方案 A 用户查看 systemd 日志：</strong></p>
<pre><code class="language-markdown">journalctl -u openclaw-gateway.service -n 50
</code></pre>
<p><strong>方案 B 用户查看日志：</strong></p>
<pre><code class="language-markdown">tail -50 /var/log/openclaw-gateway.log
</code></pre>
<h3>Q3: 怎么完全卸载？</h3>
<pre><code class="language-markdown"># 1. 停止服务

systemctl stop openclaw-gateway.service 2>/dev/null || pkill -f "openclaw gateway"
systemctl disable openclaw-gateway.service 2>/dev/null

# 2. 删除 systemd 服务文件（如有）

rm /etc/systemd/system/openclaw-gateway.service 2>/dev/null
systemctl daemon-reload 2>/dev/null

# 3. 删除 crontab 开机任务（如有）

crontab -l 2>/dev/null | grep -v "start-gateway.sh" | crontab - 2>/dev/null
rm /opt/openclaw/start-gateway.sh 2>/dev/null

# 4. 卸载 OpenClaw（保留配置和会话）

sudo -u openclaw bash -c 'source /opt/openclaw/.openclaw/.env &#x26;&#x26; openclaw uninstall' 2>/dev/null

# 5. 删除用户和目录（谨慎，会删除所有数据！）

userdel openclaw 2>/dev/null
rm -rf /opt/openclaw
rm -rf /var/tmp/openclaw-compile-cache
</code></pre>
]]></content:encoded>
    </item>
    <item>
      <title>为网站配置SSL证书</title>
      <link>https://supermortal.cn/blog/wei-wang-zhan-pei-zhi-ssl-zheng-shu</link>
      <guid isPermaLink="true">https://supermortal.cn/blog/wei-wang-zhan-pei-zhi-ssl-zheng-shu</guid>
      <pubDate>Tue, 24 Mar 2026 00:00:00 GMT</pubDate>
      <category>教程</category>
      <category>ssl</category>
      <category>nginx</category>
      <category>运维</category>
      <description>同一服务器部署两个及以上的项目时，手动为每个域名申请 SSL 证书，避免配置信息错误，Nginx 无法正确处理域名分流，导致的后部署的项目无法正确访问的问题</description>
      <content:encoded><![CDATA[<blockquote>
<p>在同一个服务器上部署两个项目时，宝塔面板一键申请SSL证书的时候，会因为重定向问题导致验证失败，所以我学习了手动部署SSL证书的方法，做了这个笔记，以docker部署的项目为例（本文的Nginx是在主机上，监听的是主机端口），通过本文，可以确保申请的证书独立对应域名，让Nginx正确处理域名分流</p>
</blockquote>
<h2>一.确定docker映射到宿主机的端口</h2>
<ol>
<li>理解端口映射</li>
</ol>
<pre><code>127.0.0.1:8088->80/tcp
        ↓       ↓  ↓
   主机端口 →  容器  协议
              端口
</code></pre>
<p><strong>含义</strong>：访问主机的 <code>8088</code> 端口 => 转发到容器的 <code>80</code> 端口</p>
<ol start="2">
<li>找到需要代理的端口，终端执行以下命令</li>
</ol>
<pre><code class="language-bash">docker ps --format "table {{.Names}}\t{{.Ports}}"
</code></pre>
<p>找到要对外访问的容器，记下它的<strong>主机端口</strong>（冒号左边的数字）</p>
<h2>二.创建 Nginx 配置文件</h2>
<p><strong>假设</strong>：</p>
<ul>
<li>你的域名：<code>yourdomain.com</code></li>
<li>Docker 容器映射的主机端口：<code>8088</code></li>
</ul>
<ol>
<li>创建配置文件</li>
</ol>
<pre><code class="language-bash">nano /usr/local/nginx/conf/conf.d/yourdomain.com.conf
</code></pre>
<ol start="2">
<li>粘贴以下内容**（只需要把 yourdomain.com 和 8088 改成实际的值即可）**</li>
</ol>
<pre><code class="language-nginx">server {
    listen 80;
    server_name yourdomain.com;

    location /.well-known/acme-challenge/ {
        root /var/www/acme;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    http2 on;
    server_name yourdomain.com;

    ssl_certificate /usr/local/nginx/conf/ssl/yourdomain.com/fullchain.pem;
    ssl_certificate_key /usr/local/nginx/conf/ssl/yourdomain.com/key.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:!MD5;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    add_header Strict-Transport-Security "max-age=31536000";

    location / {
        proxy_pass http://127.0.0.1:8088;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 300s;
    }
}
</code></pre>
<h2>三.申请 SSL 证书</h2>
<blockquote>
<p><strong>以 Let's Encrypt 证书为例</strong>！Let's Encrypt 是全球最受信赖的免费 CA，浏览器默认信任</p>
</blockquote>
<ol>
<li>先创建目录</li>
</ol>
<pre><code class="language-bash">mkdir -p /var/www/acme
mkdir -p /usr/local/nginx/conf/ssl/yourdomain.com
</code></pre>
<ol start="2">
<li>申请 Let's Encrypt 证书</li>
</ol>
<pre><code class="language-bash">/root/.acme.sh/acme.sh --issue -d yourdomain.com -w /var/www/acme --keylength ec-256 --server letsencrypt
</code></pre>
<p><strong>参数说明</strong>：
| 参数 | 含义 |
|------|------|
| <code>-d yourdomain.com</code> | 要申请证书的域名 |
| <code>-w /var/www/acme</code> | HTTP 验证目录（acme.sh 会在这个目录创建验证文件） |
| <code>--keylength ec-256</code> | 使用 ECDSA 256 位加密（更快更安全） |
| <code>--server letsencrypt</code> | <strong>指定使用 Let's Encrypt</strong>安装证书 |</p>
<ol start="3">
<li>安装证书到目录</li>
</ol>
<pre><code class="language-bash">/root/.acme.sh/acme.sh --install-cert -d yourdomain.com \
  --key-file /usr/local/nginx/conf/ssl/yourdomain.com/key.pem \
  --fullchain-file /usr/local/nginx/conf/ssl/yourdomain.com/fullchain.pem \
  --reloadcmd "nginx -s reload"
</code></pre>
<h2>四.证书到期更新</h2>
<blockquote>
<p>Let's Encrypt 的免费证书有效期是 <strong>90 天</strong>。安装的时候设置了<code>--reloadcmd</code>所以acme.sh 会在证书到期前 <strong>30 天</strong>自动续期，所以一般情况下不需要手动操作,安装时候就会自动设置定时任务每天检查</p>
</blockquote>
<ol>
<li>确认自动续期已配置</li>
</ol>
<pre><code class="language-markdown"># 终端执行命令

crontab -l | grep acme.sh

# 正常输出，这是每天13点02分自动执行

2 13 \* \* \* "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null
</code></pre>
<ol start="2">
<li>查看证书到期时间</li>
</ol>
<pre><code class="language-markdown"># 查看所有证书

/root/.acme.sh/acme.sh --list

# 查看单个证书详情

/root/.acme.sh/acme.sh --info -d 你的域名
</code></pre>
<ol start="3">
<li>如果没有正确设置到定时任务自动续期，可以手动续期</li>
</ol>
<pre><code class="language-markdown"># 强制续期（忽略是否到期）

/root/.acme.sh/acme.sh --renew -d 你的域名 --force

# 手动续期所有证书

/root/.acme.sh/acme.sh --cron --home "/root/.acme.sh"
</code></pre>
<h2>五.测试并生效</h2>
<ol>
<li>测试配置</li>
</ol>
<pre><code class="language-bash">/usr/local/nginx/sbin/nginx -t
</code></pre>
<ol start="2">
<li>看到这些就对了：</li>
</ol>
<pre><code>nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
</code></pre>
<ol start="3">
<li>重启 Nginx</li>
</ol>
<pre><code class="language-bash">/usr/local/nginx/sbin/nginx -s reload
</code></pre>
<h2>六.多个项目如何确保网站不互串</h2>
<ol>
<li>检查现有配置</li>
</ol>
<pre><code class="language-bash">ls -la /usr/local/nginx/conf/conf.d/
</code></pre>
<p>应该看到如下配置文件：</p>
<pre><code>first.top.conf
second.top.conf
third.com.conf
</code></pre>
<ol start="2">
<li>每个配置文件里的关键区别</li>
</ol>
<p>| 网站       | server_name  | proxy_pass              |
| ---------- | ------------ | ----------------------- |
| first.top  | <code>first.top</code>  | <code>http://127.0.0.1:8317</code> |
| second.top | <code>second.top</code> | <code>http://127.0.0.1:8088</code> |
| third.com  | <code>third.com</code>  | <code>http://127.0.0.1:8000</code> |</p>
<p><strong>只要 <code>server_name</code> 不同，Nginx 就会精准匹配，互不干扰！</strong></p>
<ol start="3">
<li>所以同一个服务器部署多个网站对应多个域名时，只需要在创建配置文件时候，正确填写对应的域名即可</li>
</ol>
<pre><code class="language-bash"># 1. 创建配置（改域名和端口）
nano /usr/local/nginx/conf/conf.d/实际域名.conf

# 2. 申请证书（改域名，必须加 --server letsencrypt）
/root/.acme.sh/acme.sh --issue -d 实际域名 -w /var/www/acme --keylength ec-256 --server letsencrypt

# 3. 安装证书（改域名）
/root/.acme.sh/acme.sh --install-cert -d 实际域名 \
  --key-file /usr/local/nginx/conf/ssl/实际域名/key.pem \
  --fullchain-file /usr/local/nginx/conf/ssl/实际域名/fullchain.pem \
  --reloadcmd "nginx -s reload"

# 4. 测试并重载
/usr/local/nginx/sbin/nginx -t &#x26;&#x26; /usr/local/nginx/sbin/nginx -s reload
</code></pre>
<h2>七.一键配置脚本</h2>
<blockquote>
<p>整个流程相对来说不是很复杂不是很麻烦，但为了更加方便，我写了一键部署脚本，前提是nginx是通过编译安装的，当然里面也包含了一键安装nginx的脚本，目前已经开源，开源地址<a href="https://github.com/super-mortal/vps_tools">vps_tools</a></p>
</blockquote>
<h3>1.使用方法</h3>
<pre><code class="language-markdown"># 克隆仓库

git clone https://github.com/super-mortal/vps_tools.git

# 进入目录

cd vps_tools

# 给脚本添加执行权限

chmod +x ssl_cert.sh

# 运行交互式脚本

./ssl_cert.sh
</code></pre>
<h3>2.注意事项</h3>
<pre><code class="language-markdown"># 先确认 443 端口真的是 Nginx 在监听
ss -tulnp | grep 443
# 查看 ufw 状态（Debian/Ubuntu）
ufw status
# 放行443端口（TCP协议，HTTPS标准端口）
ufw allow 443/tcp
# 重载防火墙规则，使配置生效
ufw reload
# 再次验证规则是否添加成功
ufw status
</code></pre>
<h2>八.常见问题</h2>
<h3>Q: 申请证书报错 "Can't find directory"</h3>
<pre><code class="language-bash">mkdir -p /var/www/acme
</code></pre>
<h3>Q: 申请证书报错 "Domain is not point to this server"</h3>
<p>域名 DNS 还没生效，等几分钟再试。或者检查域名 A 记录是否指向当前服务器 IP。</p>
<h3>Q: HTTPS 能访问但显示不安全</h3>
<ol>
<li>
<p>检查证书是不是 Let's Encrypt：</p>
<pre><code class="language-bash">openssl x509 -in /usr/local/nginx/conf/ssl/你的域名/fullchain.pem -noout -issuer
</code></pre>
<p>应该显示 <code>Issuer: C = US, O = Let's Encrypt</code></p>
</li>
<li>
<p>如果不是 Let's Encrypt，可能是其他的证书不受浏览器信任所以还是显示不安全，删除重新申请：</p>
<pre><code class="language-bash">/root/.acme.sh/acme.sh --remove -d 你的域名
/root/.acme.sh/acme.sh --issue -d 你的域名 -w /var/www/acme --keylength ec-256 --server letsencrypt
</code></pre>
</li>
<li>
<p>如果是 <strong>Let's Encrypt</strong> 的证书但仍然显示不安全，可能是浏览器缓存了旧的证书</p>
</li>
</ol>
<pre><code class="language-markdown">1. 清除普通缓存

- 菜单 → 隐私和安全 → 清除浏览数据

- 勾选上述两项，清除

2. 清除HSTS（强制HTTPS策略）

**地址栏输入**

- Chrome： chrome://net-internals/#hsts 

- Edge： edge://net-internals/#hsts 

- 找到 Delete domain security policies

- 输入域名（如 example.com ）→ Delete

3. 清除单个网站HTTPS缓存（精准）

- 打开网站 → 地址栏左侧锁图标 → 网站设置 → 清除数据
</code></pre>
]]></content:encoded>
    </item>
    <item>
      <title>理解服务器中的Nginx</title>
      <link>https://supermortal.cn/blog/li-jie-fu-wu-qi-zhong-de-nginx</link>
      <guid isPermaLink="true">https://supermortal.cn/blog/li-jie-fu-wu-qi-zhong-de-nginx</guid>
      <pubDate>Mon, 23 Mar 2026 00:00:00 GMT</pubDate>
      <category>知识点</category>
      <category>nginx</category>
      <category>服务器</category>
      <category>运维</category>
      <description>一文读懂什么是 Nginx 和其在服务器中的作用</description>
      <content:encoded><![CDATA[<h2>一.什么是 Nginx？</h2>
<p>打个比喻，你去饭店吃饭，前台问你定的哪个包间，这个前台在服务器上就叫 <strong>Nginx</strong></p>
<p>| 餐厅前台         | 服务器 Nginx         |
| ---------------- | -------------------- |
| 接待来吃饭的顾客 | 接待访问网站的用户   |
| 问顾客订哪个包间 | 根据域名区分请求     |
| 带顾客到包间     | 把请求转发给后端程序 |
| 把菜端给顾客     | 把处理结果返回给用户 |</p>
<h2>二.Nginx 在服务器中起什么作用？</h2>
<ol>
<li>先看看没有 Nginx 会怎样</li>
</ol>
<p><img src="meiyounginx.png" alt="手绘风示意图「没有 Nginx 的世界 - 网站访问直连后端的崩溃日常」：卡通用户拿着 HTTP Request 直接访问一台叫 Hachiware 的后端服务器，服务器冒汗过热、链接断裂崩溃，说明没有 Nginx 作为前置代理时用户请求直冲后端会造成服务不稳和安全隐患"></p>
<ol start="2">
<li>有 Nginx 之后</li>
</ol>
<pre><code>用户访问网站
           ┌───────────────────────────┐
           │        服务器              │
           │                           │
           │   ┌─────────────┐         │
           │   │  Nginx      │         │
           │   │  (前台)      │         │
           │   └──────┬──────┘         │
           │          │                │
           │          ↓                │
           │   ┌────────────┐          │
           │   │ 后端程序    │          │
           │   │ (厨师)      │          │
           │   └────────────┘          │   ->返回网页给用户
           └───────────────────────────┘
</code></pre>
<p><strong>Nginx 专门负责"接待"，后端程序专门负责"做菜"，各司其职！</strong></p>
<ol start="3">
<li>更深层次理解Nginx 具体干什么</li>
</ol>
<pre><code>┌──────────────────────────────────────────┐
│                Nginx 的工作               │
├──────────────────────────────────────────┤
│  1️⃣  接收请求                             │
│      用户访问 → Nginx 先收到               │
│                                          │
│  2️⃣  区分域名                             │
│      first.com → A网站                    │
│      second.com → B网站                   │
│      third.com → C网站                    │
│                                          │
│  3️⃣  反向代理                             │
│      把请求转发给后端程序                   │
│                                          │
│  4️⃣  处理 HTTPS                          │
│      加密解密，证书验证                    │
│                                         │
│  5️⃣  提供静态文件                         │
│      图片、CSS、JS                       │
│                                         │
│  6️⃣  返回结果                            │
│      返回结果给用户                       │
└────────────────────────────── ──────────┘
</code></pre>
<p><img src="nginxzuoyong.png" alt="手绘风示意图「网站访问全过程」：用户浏览器访问 example.com → DNS 服务器像电话簿把域名翻译成 IP 1.2.3.4 → HTTPS 加密安全通道连接到 1.2.3.4:443 → Nginx 高效前台管家接收并分发请求 → 后端程序厨师处理业务、读写数据库 → 页面返回浏览器展示，直观说明 Nginx 在服务器架构中承担反向代理与流量入口的角色"></p>
<ol start="4">
<li>前台可以同时接待很多客人，Nginx也可以同时处理不同的用户请求</li>
</ol>
<pre><code>一个前台（Nginx），同时接待多个人：

访客1 ──→ 前台 ──→ A网站
访客2 ──→ 前台 ──→ B网站
访客3 ──→ 前台 ──→ A网站    ->高效处理
</code></pre>
<h2>三.Nginx配置中的核心概念</h2>
<ol>
<li>什么是server？</li>
</ol>
<p><strong>server = 一家公司/一个网站</strong></p>
<pre><code class="language-nginx">server {
    server_name first.com;
    # 这就是 first.com 这个网站
}

server {
    server_name second.com;
    # 这就是 second.com 这个网站
}
</code></pre>
<p><strong>同一个服务器上部署多个不同的服务，分别绑定对应的域名时，这里的server_name就是对应的域名，通过这个配置，Nginx就可以清楚的知道用户需要访问的是哪个服务，比如用户输入的是second.com，Nginx就会去找second.com对应的配置文件对应的服务然后返回给用户，而不会返回first.com域名对应的服务，这就是多域名共用一个服务器（域名分流）</strong></p>
<ol start="2">
<li>什么是 location？</li>
</ol>
<p><strong>location = URL 路径/部门</strong></p>
<pre><code class="language-nginx">server {
    server_name first.com;

    location / {
        # 处理 first.com/ 的请求
    }

    location /api {
        # 处理 first.com/api 的请求
    }
}
</code></pre>
<ol start="3">
<li>什么是 proxy_pass？</li>
</ol>
<p><strong>proxy_pass = 转交/外包</strong></p>
<pre><code class="language-nginx">location / {
    proxy_pass http://127.0.0.1:3000;
    # 这个请求我不处理了
    # 转交给 http://127.0.0.1:3000
}
</code></pre>
<h2>四.Nginx在服务器中的目录</h2>
<ul>
<li>一般情况下Nginx的安装目录</li>
</ul>
<pre><code>/usr/local/nginx/                    ← Nginx 安装目录
    │
    ├── conf/                        ← 配置文件目录
    │   │
    │   ├── nginx.conf               ← 主配置文件（不用改）
    │   │
    │   └── conf.d/                  ← ⭐ 网站配置放这里
    │       │
    │       ├── first.top.conf
    │       └── second.top.conf
    │
    ├── logs/                        ← 日志目录
    │   │
    │   ├── access.log               ← 访问日志（谁来过）
    │   └── error.log                ← 错误日志（出过错）
    │
    └── sbin/                        ← 可执行文件
        │
        └── nginx                    ← 启动命令
</code></pre>
<ul>
<li>SSL 证书存放目录</li>
</ul>
<pre><code>/usr/local/nginx/conf/ssl/            ← ⭐ SSL 证书放这里
    │
    ├── first.top/                    ← first.top 网站的证书
    │   │
    │   ├── fullchain.pem             ← 完整证书链
    │   └── key.pem                   ← 私钥
    │
    └── second.top/                   ← second.top 网站的证书
        │
        ├── fullchain.pem
        └── key.pem
</code></pre>
<ul>
<li>快速查看命令</li>
</ul>
<pre><code class="language-bash"># 查看 Nginx 配置文件位置
ls -la /usr/local/nginx/conf/

# 查看所有网站配置
ls -la /usr/local/nginx/conf/conf.d/

# 查看 SSL 证书
ls -la /usr/local/nginx/conf/ssl/

# 查看日志
tail -f /usr/local/nginx/logs/access.log
tail -f /usr/local/nginx/logs/error.log

# 查看正在运行的 Nginx
ps aux | grep nginx

# 重启 Nginx
/usr/local/nginx/sbin/nginx -s stop
/usr/local/nginx/sbin/nginx
</code></pre>
<h2>五.一个请求的完整旅程</h2>
<p>以docker部署的服务为例，当你访问 <code>https://first.top</code> 时：</p>
<pre><code>第1步：DNS 解析
    你输入 https://first.top
    DNS 服务器说：IP 是 1.2.3.4.55

第2步：连接服务器
    浏览器连接到 1.2.3.4.55:443

第3步：Nginx 接待
    Nginx 收到请求
    看看是哪个域名 → first.top
    找对应的配置 → first.top.conf

第4步：处理 HTTPS
    用 first.top 的 SSL 证书加密通信

第5步：反向代理
    把请求转发到 Docker 容器的 8088 端口

第6步：Docker 容器处理
    容器里的 nginx 再转发
    给 nav-site-nodejs 处理

第7步：返回结果
    网页返回给 Nginx
    Nginx 返回给浏览器
    浏览器显示页面
</code></pre>
<h2>六.总结</h2>
<blockquote>
<p><strong>所以在服务器中，Nginx主要负责这些工作（仅本文提到）</strong></p>
</blockquote>
<ol>
<li>域名分流：一个服务器对应多个域名多个网站服务，看域名转发给不同的服务</li>
<li>静态资源处理：直接返回图片、JS、CSS，减轻后端压力</li>
<li>反向代理：把请求转给后端服务（如Java、Node.js），隐藏真实端口</li>
<li>HTTPS加密：配置SSL证书，实现安全访问</li>
</ol>
<blockquote>
<p>关键目录速查</p>
</blockquote>
<p>| 用途         | 目录                                    |
| ------------ | --------------------------------------- |
| Nginx 主配置 | <code>/usr/local/nginx/conf/nginx.conf</code>      |
| 网站配置     | <code>/usr/local/nginx/conf/conf.d/***.conf</code> |
| SSL 证书     | <code>/usr/local/nginx/conf/ssl/域名/</code>       |
| Nginx 日志   | <code>/usr/local/nginx/logs/</code>                |
| 启动命令     | <code>/usr/local/nginx/sbin/nginx</code>           |</p>
]]></content:encoded>
    </item>
    <item>
      <title>Markdown 基础语法</title>
      <link>https://supermortal.cn/blog/markdown-ji-chu-yu-fa</link>
      <guid isPermaLink="true">https://supermortal.cn/blog/markdown-ji-chu-yu-fa</guid>
      <pubDate>Sat, 21 Mar 2026 00:00:00 GMT</pubDate>
      <category>教程</category>
      <category>markdown</category>
      <category>写作</category>
      <description>Markdown 十一个基础常用语法，让我们专注内容而不是纠结排版</description>
      <content:encoded><![CDATA[<p>Markdown 是一种轻量级标记语言，排版语法简洁，让人们更多地关注内容本身而非排版，Markdown 无处不在。StackOverflow、CSDN、掘金、简书、GitBook、有道云笔记、V2EX、光谷社区等。主流的代码托管平台，如 GitHub、GitLab、BitBucket、Coding、Gitee 等等，都支持 Markdown 语法，很多开源项目的 README、开发文档、帮助文档、Wiki 等都用 Markdown 写作。</p>
<h2>一.标题</h2>
<blockquote>
<p><code>#</code>和标题之间要加一个空格，最多六级标题</p>
</blockquote>
<pre><code class="language-markdown"># 一级标题

## 二级标题
</code></pre>
<h2>二. 换行</h2>
<blockquote>
<p>在一行末尾添加<strong>两个空格</strong>或使用 HTML的<code>&#x3C;br></code> 标签实现段内换行：</p>
</blockquote>
<pre><code class="language-markdown">这是第一行  
这是第二行
</code></pre>
<h2>三. 强调</h2>
<p>| 语法           | 效果         |
| -------------- | ------------ |
| <code>**粗体**</code>     | <strong>粗体</strong>     |
| <code>*斜体*</code>       | <em>斜体</em>       |
| <code>***粗斜体***</code> | <strong><em>粗斜体</em></strong> |
| <code>~~删除线~~</code>   | ~~删除线~~   |</p>
<h2>四. 引用块</h2>
<blockquote>
<p><code>></code>和引用内容之间需要加一个空格，可以多行引用和嵌套引用</p>
</blockquote>
<p><strong>嵌套引用</strong>：</p>
<pre><code class="language-markdown">> 外层引用
>
> > 嵌套引用
</code></pre>
<h2>五. 列表</h2>
<h3>无序列表</h3>
<blockquote>
<p>可用 <code>-</code>、<code>*</code>、<code>+</code> 三种符号，效果相同，一样需要加空格</p>
</blockquote>
<pre><code class="language-markdown">- 项目一
- 项目二
  - 子项目（缩进2个空格）
    - 更深层（缩进4个空格）
</code></pre>
<ul>
<li>项目一</li>
<li>项目二
<ul>
<li>子项目
<ul>
<li>更深层</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3>有序列表</h3>
<blockquote>
<p>输出<code>1.</code>然后按空格</p>
</blockquote>
<pre><code class="language-markdown">1. 第一项
2. 第二项
3. 第三项
   1. 子列表
   2. 子列表
</code></pre>
<h3>任务列表</h3>
<pre><code class="language-markdown">- [x] 已完成
- [ ] 未完成
- [ ] 另一个未完成
</code></pre>
<ul>
<li>[x] 已完成</li>
<li>[ ] 未完成</li>
<li>[ ] 另一个未完成</li>
</ul>
<h2>六. 代码</h2>
<h3>行内代码</h3>
<blockquote>
<p>左右两个反引号包裹内容</p>
</blockquote>
<pre><code class="language-markdown">`console.log('hello')`
</code></pre>
<h3>代码块</h3>
<p>使用三个反引号包裹，并可指定语言：</p>
<pre><code class="language-markdown">​`javascript
function hello() {
  console.log('Hello!');
}
​`
</code></pre>
<h2>七. 分隔线</h2>
<p>使用三个或以上的 <code>-</code>、<code>*</code>、<code>_</code>：</p>
<pre><code class="language-markdown">---
---

---
</code></pre>
<h2>八. 链接</h2>
<pre><code class="language-markdown">[超级凡人](https://supermortal.cn)
</code></pre>
<p><a href="https://supermortal.cn">超级凡人</a></p>
<h2>九. 图片</h2>
<pre><code class="language-markdown">![替代文字](图片URL)
</code></pre>
<h2>十. 表格</h2>
<pre><code class="language-markdown">| 表头1  | 表头2 |  表头3 |
| :----- | :---: | -----: |
| 左对齐 | 居中  | 右对齐 |
| 内容1  | 内容2 |  内容3 |
</code></pre>
<p>| 表头1  | 表头2 |  表头3 |
| :----- | :---: | -----: |
| 左对齐 | 居中  | 右对齐 |
| 内容1  | 内容2 |  内容3 |</p>
<p><strong>对齐说明</strong>：</p>
<ul>
<li><code>|:---|</code> 左对齐</li>
<li><code>|:---:|</code> 居中</li>
<li><code>|---:|</code> 右对齐</li>
</ul>
<h2>十一. 嵌入视频</h2>
<h3>YouTube</h3>
<pre><code class="language-markdown">&#x3C;iframe width="560" height="315" src="https://www.youtube.com/embed/视频ID" frameborder="0" allowfullscreen>&#x3C;/iframe>
</code></pre>
<h3>Bilibili</h3>
<pre><code class="language-markdown">&#x3C;iframe src="//player.bilibili.com/player.html?bvid=BV号&#x26;page=1" width="560" height="315" frameborder="0" allowfullscreen>&#x3C;/iframe>
</code></pre>
<h2>快速参考</h2>
<p>|  功能  |       语法       |
| :----: | :--------------: |
|  标题  | <code># </code> - <code>###### </code> |
|  粗体  |    <code>**文字**</code>    |
|  斜体  |     <code>*文字*</code>     |
| 删除线 |    <code>~~文字~~</code>    |
|  链接  |  <code>[文字](URL)</code>   |
|  图片  |  <code>![文字](URL)</code>  |
| 代码块 | <code>```语言 ```</code>  |
|  引用  |       <code>> </code>       |
|  列表  |  <code>- </code> 或 <code>1. </code>   |
| 分隔线 |      <code>---</code>       |</p>
]]></content:encoded>
    </item>
    <item>
      <title>我的第一篇博客</title>
      <link>https://supermortal.cn/blog/my-first-blog</link>
      <guid isPermaLink="true">https://supermortal.cn/blog/my-first-blog</guid>
      <pubDate>Tue, 17 Mar 2026 00:00:00 GMT</pubDate>
      <category>笔记</category>
      <category>建站</category>
      <category>随笔</category>
      <description>我的第一篇博客诞生——不必借光而行，你我亦是星辰</description>
      <content:encoded><![CDATA[<p>初衷很简单：一是记录自己的成长轨迹，把学习过程、技术笔记沉淀下来，方便日后回顾复盘；二是在跟着各类实战教程学习时，常常发现步骤不够完整、细节缺失，导致实操中频频踩坑。我希望把自己踩过的坑、解决过的问题，用更细致、更易懂的方式整理出来，不仅督促自己深度学习，也能帮到和我一样在学习路上摸索的人，让知识传递更踏实、更有用</p>
<p><strong>2026-09-06：</strong> 博客<a href="https://github.com/super-mortal/my-blog/blob/main/README.md">全面升级</a>：支持自定义 slug ，增加 AI 支持等</p>
<p><strong>2026-03-17：</strong> 新博客成立，使用 Astro 框架 astro-theme-pure 模板构建</p>
<p><strong>2026-01-28：</strong> 博客迁移重构，使用 Astro 框架 Firefly 模板重新构建</p>
<p><strong>2025-11-24：</strong> 博客正式成立，使用 typecho 框架的 wayxz 主题构建</p>
]]></content:encoded>
    </item>
  </channel>
</rss>