docs: 同步 EC2 栈删除后的文档
ci-pangolin / Lint — shellcheck (push) Has been cancelled
ci-pangolin / OpenAPI Sync Check (push) Has been cancelled
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Has been cancelled
ci-pangolin / Flutter — analyze + test (push) Has been cancelled

- CLAUDE.md 重写:去掉 EC2/marzban 部署、架构、cutover/rollback 命令、自动部署 CD;
  改为自有 VPS 自建方向(RackNerd Debian 12、ssh racknerd 免密、512MB→SQLite)、
  补 server/ Go 后端与多数据库(DB_DRIVER)说明;保留设计 token 单源段
- 删 docs/回滚手册.md(EC2 edge 切换/回滚,脚本已删)
- 删 docs/备份与灾备.md(EC2 MySQL/marzban xtrabackup→S3,backup 栈已删)
- app/kernel/poc/README.md:REALITY 参数来源由 EC2 改为自建节点(single-node)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-18 07:01:13 +08:00
parent bf81c786b2
commit 53caaf54f0
4 changed files with 57 additions and 580 deletions
+51 -78
View File
@@ -4,87 +4,66 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## 这个仓库是什么
Pangolin 是面向中国大陆用户的终端 VPN(科学上网)项目。本仓库**不是应用代码**,而是:
Pangolin 是面向中国大陆用户的终端 VPN(科学上网)项目,**自研全栈**:
- `deploy/` —— 数据/分发面的容器化部署(Docker + Gitea Actions CI/CD)
- `docs/` —— 设计与运维文档(`技术方案.md``回滚手册.md`)
- `plan/` —— 分阶段实施计划(phase-0 → phase-3 + 安全/获客横切)
- `server/` —— Go 控制面 + 节点 agent + sing-box 数据面(HTTP API + gRPC mTLS)
- `client/` —— Flutter 客户端(内嵌 sing-box + TUN 全局代理)
- `web/` —— 用户中心(usercenter)+ 官网(website)
- `deploy/` —— 节点部署:`bootstrap/`(新机初始化)+ `single-node/`(整套自建栈)
- `design/` —— 设计 token 单源 + 规格;`docs/` / `plan/` —— 文档与计划
客户端(Flutter)与后端面板尚未进入本仓库;当前实质内容是 `deploy/`
> 历史:早期借 EC2 上的 Marzban 临时顶现网。**本仓库已不再管理那套 EC2/marzban 部署**
> (相关 `deploy/edge|singbox|scripts|docker-compose.yml` 与 Gitea 自动部署 CD 已删除),
> 全面转向在自有 VPS 上跑自研栈。
## 部署目标与不可破坏的约束(最重要)
## 部署目标:自有 VPS 节点
部署目标是**一台已在生产运行的 EC2**(`18.136.60.128`,ap-southeast-1,user `ec2-user`),上面还跑着**与本项目无关、不能搞坏**的服务:`marzban`(线上 REALITY 代理 + 面板)、`jiu`(jiu_mysql + jiu.service :8080 + 静态)、`blog`(blog/umami/postgres)、`billing.service` :18080
当前节点:**RackNerd VPS `107.172.55.251`**(Debian 12 bookworm,root,1 核 / 512MB / 2G swap)
- **线上代理 = Marzban 的 REALITY(443 → 127.0.0.1:10443),绝不能中断。** 运维者本人通过 Shadowrocket 连的就是它
- 切换/重建 `pangolin-edge`(占用 443)会有 **~1-2 秒** TCP 交接,代理客户端会自动重连——选空闲时段做
- **注意自噬隧道**:运维者本机常挂 VPN,且该 VPN 可能正穿这台的 443。重建 edge 时 443 闪断会**把你自己的 SSH 也掐了**(SSH 经代理隧道)。重建后等几秒重连即可,不是故障
- 从运维者 Mac 做"外部"验证不可信(fake-IP DNS / 经 Cloudflare)。**权威验证一律在 EC2 内部做**(`ssh ec2` 后 curl `127.0.0.1` / openssl `127.0.0.1`)。
- **SSH 免密**:本机 `~/.ssh/config` 有别名 `racknerd`(密钥登录,密码登录已禁)→ 直接 `ssh racknerd`
- 已用 `deploy/bootstrap/init.sh` 加固(ufw + fail2ban + BBR + swap + pangolin 用户)
- **512MB 内存是硬约束**:控制面用 **SQLite**(见下),不要在这上面跑 MySQL 8
- 改任何机器系统(装包/改配置/重装)前**先问用户**(只读操作除外)。
## 目标主机的关键事实(代码里看不出来)
- 部署目录:**`~/pangolin`(即 `/home/ec2-user/pangolin`)**,不是 `/opt`
- `ec2-user` **免 sudo 跑 docker**;但**通用 `sudo` 需要密码**(Claude 跑不了)。需要 root 的步骤(如 `systemctl stop nginx`)必须让用户执行;`crontab` 在用户设置里被 deny。
- Compose 是**独立 `docker-compose`**(无 `docker compose` v2 插件),且**不支持 `compose build`**。因此:
- 命令一律用 `docker-compose -p pangolin ...`
- edge 镜像用 **`docker build -t pangolin-edge:local ./edge`** 单独构建,compose 只 `up`(不要 `--build`)。
- `edge``singbox`**`network_mode: host`**(才能绑 80/443/udp443 并回连一堆只听 `127.0.0.1` 的后端)。
- 证书在 `/etc/letsencrypt`(Let's Encrypt:blog/jiu/vpn.51yanmei.com),**rw 挂进 edge**。
## 架构(部署后)
```
EC2 :443/tcp + :80 → pangolin-edge (自定义镜像: nginx + certbot)
stream ssl_preread 按 SNI 分流(edge/stream.conf):
www.cloudflare.com/default → 10443 Marzban REALITY(线上,勿断)
vpn/blog/jiu.51yanmei.com → 8443/8444/8445(edge 内 http vhost)
http vhost(edge/conf.d/*)→ marzban面板8899 / blog3000+umami3001 / jiu8080 + 静态
EC2 :443/udp → pangolin-singbox (Hysteria2)
```
- `pangolin-edge`:分发层。**自定义镜像自带 certbot,容器内每 12h 自动 DNS-01 续期 + 本容器内 `nginx -s reload`**(见 `deploy/edge/pangolin-entrypoint.sh`)——无宿主 cron、无 docker socket。
- `pangolin-singbox`:Hysteria2 加速线(UDP 443)。
- `pangolin-xray`:REALITY,**本台不启用**(compose `profile: newnode`,留给新节点;本台 REALITY 仍由 Marzban 提供)。
- `certbot` 服务:仅 `profile: certbot`,用于**手动**首次签发/重配;日常续期由 edge 自身完成。
`deploy/edge/conf.d/*.conf``stream.conf` 是**现网宿主 nginx 配置的逐字拷贝**——改动需与现网后端地址保持一致。镜像版本固定(nginx 1.27 / sing-box v1.13.12 / xray 26.3.27)。
## 常用命令(在 `deploy/` 下)
## deploy/ 结构
```bash
# 幂等部署(免 root):起 singbox;宿主 nginx 已停则构建+起 edge,否则只校验 edge 配置
./scripts/deploy.sh
# ① 新机初始化(幂等,root 跑一次;后续每台新机复用)
# 远程:scp -r deploy/bootstrap racknerd:/root/ && ssh racknerd 'cd /root/bootstrap && bash init.sh'
deploy/bootstrap/init.sh # 加固/swap/BBR/pangolin用户/ufw/fail2ban/Telegram监控
deploy/bootstrap/monitor/ # pangolin-monitor(节点自报)+ deadman-watch(常在线主机探活)
# 一次性切换:宿主 nginx → edge 容器(需 root,用户执行;失败自动回滚)
sudo bash scripts/cutover.sh
# 回滚:停 edge,恢复宿主 nginx(需 root,用户执行)
sudo bash scripts/rollback.sh
# 仅构建 edge 镜像 / 重建 edge(会有 ~1-2s 443 闪断)
docker build -t pangolin-edge:local ./edge
docker-compose -p pangolin up -d edge
# 离线校验 edge 配置(不绑端口,挂真实证书)
docker run --rm -v "$PWD/edge/nginx.conf:/etc/nginx/nginx.conf:ro" \
-v "$PWD/edge/stream.conf:/etc/nginx/stream.conf:ro" -v "$PWD/edge/conf.d:/etc/nginx/conf.d:ro" \
-v /etc/letsencrypt:/etc/letsencrypt:ro -v /opt/jiu:/opt/jiu:ro -v /var/www/pay:/var/www/pay:ro \
nginx:1.27-alpine nginx -t
# 验证证书自续期(staging,不动正式证书)
docker exec pangolin-edge certbot renew --dry-run
# 取客户端导入串(Hy2,含口令,不进日志)
cat secrets/clients.txt
# 脚本改动后:本地 shellcheck -S warning deploy/scripts/*.sh
# ② 整套自建栈(一台 VPS 跑 MySQL/Redis + 控制面 + agent + sing-box,端到端真连)
sudo VPS_IP=<公网IP> bash deploy/single-node/deploy.sh
```
`deploy/bootstrap/bootstrap.env``/etc/pangolin-monitor.env``deploy/single-node`
`/etc/pangolin*` 密钥均 **不入 git**
## server/ 后端(Go)
- 二进制:`cmd/{server,agent,nodectl,migrate}`;`go build ./...` 直接编译,免确认。
- 控制面 HTTP API(`:8080`)+ gRPC agent 服务(`:9443`, mTLS);agent 自 enroll → 渲染
sing-box 配置 → `systemctl restart sing-box`。客户端连节点真实出网。
### 数据层:多数据库(一个环境变量切换)
server 已与具体 DB 解绑(裸 SQL + 薄方言层,`internal/db/dialect.go`):
- `DB_DRIVER=mysql`(默认)| `sqlite`;`DB_DSN`(mysql DSN / sqlite 文件路径或 `:memory:`)。
- 迁移分 `server/migrations/{mysql,sqlite}/` 两套,`migrate` 按驱动选源(`golang-migrate`)。
- SQLite 用 `modernc.org/sqlite`(纯 Go 免 CGO)+ `_txlock=immediate`(等价 MySQL 行锁的悲观语义)。
- 测试:`go test ./...`(含 SQLite 实库测试,免 docker)+ `./server/run_sqlite_test.sh`;
MySQL 集成测试 `./server/run_mysql_test.sh`(需 docker)。
- **改 schema/查询**:upsert 用 `dialect.Upsert(...)`(中性 `EXCLUDED.col` 记法),行锁用
`dialect.LockForUpdate()`;时间等一律 Go 端算好传 `?`,**不要**用 `UTC_TIMESTAMP()`/
`NOW()`/`FIELD()` 等 MySQL 专属构造(已全部清除,加回会破坏可移植性)。
## CI/CD
`push deploy/**` → NAS 上的 Gitea(`ssh://git@192.168.3.200:2222/wangjia/pangolin.git`)→ Gitea Actions runner → `ssh ec2``rsync deploy/ → ~/pangolin/``deploy.sh`(免 root,幂等)。
- 需在 Gitea 配 Secrets:`EC2_SSH_KEY` / `EC2_HOST` / `EC2_USER` / `CF_API_TOKEN`,并把 `.gitea/workflows/deploy.yml``runs-on` 改成实际 runner label。
- 手动等价流程:`rsync -az --exclude secrets deploy/ ec2:pangolin/ && ssh ec2 'cd ~/pangolin && ./scripts/deploy.sh'`
`.gitea/workflows/ci.yml`(runner label `nas`):shellcheck(bootstrap/single-node 脚本)、
OpenAPI 结构校验、UI 文案脱敏扫描、Flutter analyze+test。**仅校验,无部署动作。**
- 节点部署是手动/按需的(scp+ssh 跑 bootstrap / single-node),不走 CI 自动推送
## 设计 Token 单源模型
@@ -94,18 +73,12 @@ cat secrets/clients.txt
# Flutter token(生成 client/lib/pangolin_tokens.gen.dart
node design/codegen/gen_flutter_tokens.mjs
# usercenter CSS token(由 prebuild/predev 钩子自动触发,也可手动)
# usercenter / website CSS token(由 prebuild/predev 钩子自动触发,也可手动)
cd web/usercenter && npm run gen:tokens
# website CSS token(由 prebuild/predev 钩子自动触发,也可手动)
cd web/website && npm run gen:tokens
cd web/website && npm run gen:tokens
```
- `client/lib/pangolin_tokens.gen.dart`**勿手改**,由生成器覆盖。
- `client/lib/pangolin_theme.dart` — 只含实现层(`PangolinScheme`/`PangolinText`/`PangolinTheme`),不含 token 数值。
- `design/flutter/` 目录已删除;Flutter 组件 canonical 实现在 `client/lib/widgets/`,规格在 `design/preview/`
- `client/lib/pangolin_theme.dart` — 只含实现层(`PangolinScheme`/`PangolinText`/`PangolinTheme`),不含 token 数值。
- `design/flutter/` 已删除;Flutter 组件 canonical 实现在 `client/lib/widgets/`,规格在 `design/preview/`
- **禁止**再向 `design/` 提交 Dart/TS 组件代码副本(会漂移)。
## 密钥与渲染产物
`deploy/secrets/``*/config.json``cloudflare.ini`**gitignore**`gen-secrets.sh` 在 EC2 首跑生成 Hy2 口令/自签证书;`CF_API_TOKEN`(本机 `~/.env` 已加载,CI 由 Secret 注入)渲染 `cloudflare.ini`。模板用 `__PLACEHOLDER__`,脚本 `sed` 渲染。