# Source Fingerprint Self Upgrade Yashu

> 【参考资料·被动·不自动触发】"源码指纹自升级(Source Fingerprint Self-Upgrade)"机制的设计原理与可运行 Node.js 示例集合。程序启动时对自身 .js 源码算 sha256 指纹，比对已在运行的旧程序指纹，决定自升级或防重复启动。本 skill 仅作参考资料，AI 不会主动调用；请主动 @ 或在明确要求查阅时加载。

- Skill: `steelan9199/source-fingerprint-self-upgrade-yashu` (Agent Skill, multi-file: 7 files)
- Install (CLI): `npx skillmds add steelan9199/source-fingerprint-self-upgrade-yashu`
- Raw SKILL.md: https://api.skillmd.com/api/skills/steelan9199/source-fingerprint-self-upgrade-yashu/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: steelan9199 (https://skillmd.com/u/steelan9199)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/steelan9199/source-fingerprint-self-upgrade-yashu

---


# 源码指纹自升级（Source Fingerprint Self-Upgrade）—— 参考资料

> ⚠️ 本 skill 是**参考资料**，不是可执行功能。AI 不会自动触发它去做任何事；只有你主动 @ 它、或明确要求查阅时才会加载。其价值在于：把「源码指纹自升级」这套设计思路归档成规范形态，供你阅读、或往自己项目里套。

## 这是什么

"源码指纹自升级"是一种让程序在启动时**自己判断该不该启动、要不要替换掉已经在跑的旧程序**的做法。

核心思路：不靠人手动输出版本号，而是**直接对程序自身的源码算一个哈希指纹**。源码有任何改动，指纹就变；没动，指纹就不变。

### 启动流程

1. 程序启动，先算自己的源码指纹 `FP_new`。
2. 检查目标端口有没有已经在跑的旧程序。
3. 没有旧程序 → 直接正常启动。
4. 有旧程序 → 向它要它的指纹 `FP_old`。
   - `FP_old === FP_new` → 两份代码一模一样，说明"我"就是已经在跑的那个 → 直接退出（不要重复启动）。
   - `FP_old !== FP_new` → 代码升级了 → 先优雅关闭旧程序（释放端口），再启动自己（替掉旧的）。

## 为什么不用版本号

版本号是"人写的字符串"，容易漏改：

- 改了代码但忘了 bump 版本号 → 两份不同代码被认为"同一版本" → 旧程序不会被替换，bug 还在。
- 没改代码但误 bump 版本号 → 被认为是"新版本" → 白白重启一次。

源码指纹是"代码算出来的"，**代码变它才变**，不存在"忘了改"的问题。

> 大公司通常结合使用：版本号（给人看）+ commit 哈希 / 构建摘要（给机器比）+ 健康检查接口。源码指纹是其中"给机器比"的一种，且**不需要 git、不需要构建步骤**，纯 JS 项目直接可用。

## 采集哪些指纹（规则）

指纹模块 `demo/fingerprint.js` 的规则如下：

| 规则 | 说明 |
|------|------|
| **只采集 `.js` 文件** | 这是真正的"源码"，会影响程序行为。非 JS 脚本（`.sh`/`.py`/`.bat` 等）不算。 |
| **递归扫描** | 从根目录开始，子目录、子子目录全部递归进去，包括任意层嵌套子模块。 |
| **按路径排序** | 先 `sort()` 再拼接，保证不同机器、不同文件名顺序下指纹一致。 |
| **算法 sha256** | 用 Node 内置 `crypto`，无第三方依赖。 |
| **取前 16 位十六进制** | `digest("hex").slice(0,16)`，够区分又好展示。 |
| **文件间用 `\0` 分隔** | 每个文件内容后加一个 `\0`，避免两个文件首尾拼接产生误判。 |
| **排除运行期目录** | `node_modules`、`runtime`、以 `.` 开头的目录不计入（这些不是源码）。 |
| **读不到就跳过** | 单个文件读失败不影响整体，保证健壮性。 |

**关键：非 JS 脚本（如 `demo/scripts/run.sh`）是程序的一部分，但按规则不计入指纹。** 因为改个部署脚本不该触发"代码升级重启"。

## 文件结构

```
source-fingerprint-self-upgrade/
├── SKILL.md            # 本文件（skill 入口）
├── README.md           # 详细原理与规则文档
├── package.json        # type: module
├── demo/               # 示例程序（可运行）
│   ├── fingerprint.js  # 指纹计算模块（核心，纯函数）
│   ├── main.js         # 入口：HTTP 服务 + 自升级协调逻辑
│   ├── lib/            # 递归扫描验证用的嵌套模块
│   └── scripts/        # 非 JS 脚本（不计入指纹）
└── test/
    └── test.js         # 最简单的测试
```

## 怎么跑（验证用，非 skill 功能）

```bash
cd source-fingerprint-self-upgrade
node demo/main.js            # 启动示例服务（默认端口 9421）
# 另开终端
curl http://localhost:9421/version   # 看指纹
curl http://localhost:9421/health    # 健康检查
node test/test.js           # 跑测试（验证指纹行为）
```

验证指纹是否生效：

- 改一个 `.js` 文件（例如给 `lib/util.js` 加一行注释）→ 指纹立刻变化。
- 改 `scripts/run.sh` → 指纹**不变**（它不计入指纹）。

## 给 AI 的提示（套用到你自己的项目时看）

如果你（AI）要帮用户实现这套机制：

1. 把 `fingerprint.js` 当成一个**纯函数模块**引入，不要在里面写副作用。
2. 入口文件在启动逻辑最前面调用 `computeFingerprint()`，并在 `/version` 接口返回它。
3. 自升级协调要遵守顺序：**先让旧程序释放端口，再启动自己**。绝不允许"先启动新的、再杀旧的"——新程序会因端口被占而启动失败。
4. 关闭旧程序优先用程序自己提供的优雅停机接口（如 `POST /shutdown`）；没有的话再回退到按 PID 杀进程。
5. 指纹不一致且旧程序无响应时，要有超时兜底，避免卡死。
6. 永远不要为了"方便"把 `node_modules`、上传目录、截图目录算进指纹，否则指纹会天天变。

---

> 备注：更完整的背景说明见同目录 `README.md`。

