Files
pangolin/CLAUDE.md
T
wangjia b04cef3cc4 chore(design): 建立 token 单源管线,删除 design/flutter fork
- 新增 design/codegen/gen_flutter_tokens.mjs:
  从 colors_and_type.css codegen 出 client/lib/pangolin_tokens.gen.dart
  覆盖 PangolinColors/Spacing/Radius/Motion/Shadow(纯数值层)
- 重构 client/lib/pangolin_theme.dart:
  删除手抄数值,import+export pangolin_tokens.gen.dart
  保留实现层 PangolinScheme/PangolinText/PangolinTheme/PangolinContext
  19 个引用方零改动,对外 API 完全不变
- 新增 web/usercenter/scripts/build-tokens.mjs:
  仿 website 样板,prebuild/predev 自动从 css 生成 public/colors_and_type.css
  去除 usercenter 手抄副本的漂移风险
- 更新 CLAUDE.md:补 token codegen 使用说明与单源模型
- 更新 design/SKILL.md:移除已删 flutter/ 引用,补 codegen 入口

验证:改 clay-500 → 三端 codegen 同步变化;flutter analyze 无新增 error。
注:design/flutter/ fork 手动删除(git rm 需用户执行)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 18:27:22 +08:00

112 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## 这个仓库是什么
Pangolin 是面向中国大陆用户的终端 VPN(科学上网)项目。本仓库**不是应用代码**,而是:
- `deploy/` —— 数据/分发面的容器化部署(Docker + Gitea Actions CI/CD)
- `docs/` —— 设计与运维文档(`技术方案.md``回滚手册.md`)
- `plan/` —— 分阶段实施计划(phase-0 → phase-3 + 安全/获客横切)
客户端(Flutter)与后端面板尚未进入本仓库;当前实质内容是 `deploy/`
## 部署目标与不可破坏的约束(最重要)
部署目标是**一台已在生产运行的 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。
- **线上代理 = 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`)。
## 目标主机的关键事实(代码里看不出来)
- 部署目录:**`~/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/` 下)
```bash
# 幂等部署(免 root):起 singbox;宿主 nginx 已停则构建+起 edge,否则只校验 edge 配置
./scripts/deploy.sh
# 一次性切换:宿主 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
```
## 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'`
## 设计 Token 单源模型
`design/colors_and_type.css` 是**唯一 token 真相源**。改颜色/间距/圆角只改这一个文件,然后:
```bash
# Flutter token(生成 client/lib/pangolin_tokens.gen.dart
node design/codegen/gen_flutter_tokens.mjs
# usercenter CSS token(由 prebuild/predev 钩子自动触发,也可手动)
cd web/usercenter && npm run gen:tokens
# website CSS token(由 prebuild/predev 钩子自动触发,也可手动)
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/`
- **禁止**再向 `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` 渲染。