CodeBuddy 的记忆原本只存在本机用户目录(不进版本控制),换机器即丢失。 把 5 个记忆文件备份进仓库,clone 后让助手「从 .ai-memory/ 导入记忆」即可恢复。 - README 说明用途、导入方法与维护约定 - CODEBUDDY.md 补充同步约定(改完记忆须同步本目录) - 内容仅含基础设施信息(IP/域名/用户名),无任何密码或密钥
144 lines
10 KiB
Markdown
144 lines
10 KiB
Markdown
---
|
||
name: 生产服务器部署环境
|
||
description: ethereal-realm.top 的部署拓扑、自建 Git 代码服务器(47.93.46.28:1216,已弃用 GitHub)、MySQL/nginx 实际配置与几个反直觉的坑(pypi 不可达、MySQL 非 Docker、skip_name_resolve、nginx 只代理部分路径)
|
||
type: project
|
||
---
|
||
|
||
生产服务器 `deploy@ethereal-realm.top`(Alibaba Cloud Linux 3,公网 `39.107.55.179`,内网 `172.17.151.49`)的实际环境,与 REQUIREMENTS.md 的描述有出入。
|
||
|
||
**部署拓扑**
|
||
- 应用代码:`/home/deploy/app`(git 仓库,remote 指向自建 Git 服务器,见下)
|
||
- 虚拟环境:`/home/deploy/venv`(Python 3.11),**不是** 项目目录下的 venv
|
||
- 服务:systemd `wechat-api.service`(`/etc/systemd/system/`),`ExecStart=/home/deploy/venv/bin/python /home/deploy/app/run_server.py`,`Restart=always`,监听 `127.0.0.1:8000`
|
||
- 入口:systemd `cloudflared-quick.service`(`cloudflared tunnel --url http://127.0.0.1:80`,
|
||
快速隧道,无配置文件、无路径限制,`Restart=always`,`User=deploy`)→ nginx:80 → 应用。
|
||
**注意服务名是 `cloudflared-quick` 不是 `cloudflared`**——查 journal 时用错名字会显示空,误判成裸进程。
|
||
- nginx 配置:`/etc/nginx/conf.d/wechat-api.conf`,`server_name ethereal-realm.top`
|
||
- `deploy` 在 `wheel` 组,`sudo` 需要密码 → 涉及 sudo 的操作必须让用户手动执行
|
||
|
||
**代码仓库拓扑(2026-09-27 起)**
|
||
|
||
用户自建了一台 Linux 代码服务器 `gjm@47.93.46.28:1216`(CentOS 7.2,git 1.8.3.1),
|
||
裸仓库在 `/srv/git/wx-scan-authorize.git`(owner `gjm`)。
|
||
|
||
```
|
||
本地 d:/wx-scan-authorize ──push──> 代码服务器 47.93.46.28 (唯一远端) ──pull──> 应用服务器 ~/app
|
||
```
|
||
|
||
- 本地:只有一个远端 `origin` = 代码服务器。`git push` / `git pull` 不带参数即可。
|
||
- 应用服务器:`origin`=`ssh://gjm@47.93.46.28:1216/srv/git/wx-scan-authorize.git`,
|
||
直接 `git pull`。两端读写都已验证。
|
||
- **GitHub 已弃用**(2026-09-27 用户决定):国内访问不稳定,不再作备份。本地 remote
|
||
已删除,`origin` 这个名字改指代码服务器。GitHub 上那个仓库(`LittleGuo/wx-scan-authorize`)
|
||
是否删除由用户决定。
|
||
- 代码服务器上 `gjm` **没有免密 sudo**,涉及 root 的操作(装软件等)要让用户做。
|
||
- 该裸仓库 `HEAD` 曾误指 `refs/heads/master`(`git init --bare` 默认值),已改为
|
||
`refs/heads/main`。以后新建裸仓库记得顺手改,否则 clone 会拿到空工作区。
|
||
|
||
**域名被阿里云 ICP 拦截(2026-09-27 确认)**
|
||
|
||
> 用户已于 2026-09-27 提交 ICP 备案,预计不久下来。因此**决定不搞 Cloudflare 命名隧道**
|
||
> (那需要账号 + 把 ethereal-realm.top 的 NS 改到 Cloudflare,代价不值)。隧道只是过渡,
|
||
> 备案通过后应把公众号后台 URL 改回 `http://ethereal-realm.top/wechat` 并撤掉隧道。
|
||
|
||
`http://ethereal-realm.top` 在公网访问会返回阿里云的拦截页
|
||
(403 `Non-compliance ICP Filing`),因为备案还没做。**直连公网 IP
|
||
`http://39.107.55.179` 一切正常**(nginx 的 `/auth/`、`/usage/` 都能打到应用)。
|
||
影响面:
|
||
|
||
- 微信服务器的回调推送也进不来 —— 公众号后台「服务器配置」URL 若指向该域名,
|
||
`subscribe`/`SCAN` 事件永远到不了应用(journal 里完全没有 `/wechat` POST)。
|
||
- 可用的公网入口是 Cloudflare **快速隧道**(`cloudflared-quick.service`,已 enabled +
|
||
`Restart=always`,所以 SSH 断开/进程崩溃/服务器重启都能自动拉起)。隧道 URL 形如
|
||
`https://<随机词>-<随机词>-<随机词>-<随机词>.trycloudflare.com`,**每次重启都会变**
|
||
(这是快速隧道的固有行为,不是配置问题),捞当前值:
|
||
`journalctl -u cloudflared-quick | grep -oE "https://[a-z0-9-]+\.trycloudflare\.com" | tail -1`。
|
||
公众号后台的服务器配置要填 `<隧道URL>/wechat`。若推送突然不来了,先查这个 URL 是否变过。
|
||
|
||
**微信真实推送已打通(2026-09-27 19:12 验证)**
|
||
|
||
绕开 ICP 的办法:把测试号后台的服务器配置 URL 指向 Cloudflare 快速隧道,
|
||
`https://catering-thumbnails-pirates-workstation.trycloudflare.com/wechat`(Token 不变)。
|
||
微信随即发来 GET 校验(来源 `162.62.81.123`,腾讯云网段),签名复算一致、返回 200,
|
||
后台配置成功。之后真实扫码产生了第一条**非模拟**推送:
|
||
|
||
```
|
||
162.62.80.57 - "POST /wechat?signature=...×tamp=...&nonce=...&openid=ohve625WiuQVosBDGLVwxGT68S4c" 200 OK
|
||
```
|
||
|
||
整条链路结果(库内):新建 `users.id=2`(真实 openid `ohve625WiuQVosBDGLVwxGT68S4c`),
|
||
发 7 天免费时间授权(`authorizations.id=2`,`2026-09-27 19:12:43 → 2026-10-04 19:12:43`),
|
||
`auth_scenes.id=9` 置 authorized,`usage_logs.id=2` 记账一条。
|
||
|
||
- **微信推送 URL 里会带一个 `openid=` 查询参数**(官方文档没写)。我们忽略它——
|
||
`verify_signature()` 只用 token/timestamp/nonce,业务用 XML 正文的 `FromUserName`。
|
||
已确认代码里没有任何地方往 query 拼 openid,是微信自己加的。
|
||
- 隧道**已经是 systemd 服务** `cloudflared-quick.service`(enabled + `Restart=always`),
|
||
服务器重启/进程崩溃都会自动拉起,公众号后台配置不会因进程死掉而失效。
|
||
但快速隧道 URL **每次重启仍会变**——所以重启后仍要重新捞 URL 并回填公众号后台。
|
||
- 库里的早期手工测试数据(`users.id=1 / openid='oTEST_CLIENT_01'`,其
|
||
`authorizations.id=1` 的 `end_at` 早于 `start_at`)**已于 2026-09-27 20:19 清理干净**,
|
||
连同 9 条无归属的过期测试场景。清理前整库备份在
|
||
`/home/deploy/backup-cleanup-20260927-201903.sql`(11 KB)。
|
||
现在库里只剩真实数据:`users.id=2`(openid `ohve625WiuQVosBDGLVwxGT68S4c`)。
|
||
- **`end_at` 倒挂不是 bug**(这条结论仍然成立):生产代码路径
|
||
(`_grant_free_authorization`)在同一条 INSERT 里取 `NOW()` 算 start/end,不可能倒挂。
|
||
- **清理用户数据时注意 `auth_scenes` 的删除规则是 `SET NULL` 不是 `CASCADE`**
|
||
(`authorizations`/`usage_logs`/`sessions` 才是 CASCADE)。直接删 `users` 会在
|
||
`auth_scenes` 留下一条 `user_id=NULL` 的孤儿记录,状态可能还是 `authorized`——
|
||
客户端轮询时可能读到。**正确顺序是先删该用户的 `auth_scenes`,再删 `users`**。
|
||
|
||
**几个反直觉的坑**
|
||
|
||
1. **服务器连不上 pypi.org**(超时),但阿里云镜像正常(0.06s)。装包必须加
|
||
`-i https://mirrors.aliyun.com/pypi/simple/`,否则 pip 会长时间挂住。
|
||
|
||
2. **MySQL 是原生安装的 8.0.46,不是 REQUIREMENTS 里写的 Docker**。且
|
||
`skip_name_resolve=ON` + `bind_address=127.0.0.1`,导致 `root@localhost`
|
||
匹配不了 TCP 连接(报 `ERROR 1130 Host '127.0.0.1' is not allowed`)。
|
||
应用用专用账号 `wechat@127.0.0.1`(只授权 `wechat_api.*`),凭据在服务器 `.env` 里。
|
||
注意:本机 Windows 的 `.env` 仍是 `MYSQL_USER=root`,连的是本机 MySQL,两份不一样。
|
||
|
||
3. **nginx 有两个 server 块,访问行为取决于 Host 头**(2026-09-27 发现,很反直觉):
|
||
|
||
| 入口 | 匹配的 server 块 | 行为 |
|
||
|---|---|---|
|
||
| `Host: ethereal-realm.top` | `wechat-api.conf` | **精确白名单**:只放行 `/wechat`、`/auth/`、`/usage/`、`/admin`,其余落到 `location /` 的占位响应 `return 200 'wechat api ok'` |
|
||
| `Host: 39.107.55.179`(IP 直连)| `app.conf` | **全量代理**:`listen 80 default_server` + `location / { proxy_pass http://fastapi; }`,所有路径都转发到 8000 |
|
||
|
||
`app.conf` 是 Sep 25 就存在的早期配置(不是本次部署引入)。后果:
|
||
- **IP 直连会暴露 FastAPI 的 `/docs` 与 `/openapi.json`**(实测 200 + 真实 schema);
|
||
域名路径下这两个路径返回占位文本,不暴露。
|
||
- MFC 插件当前 `-DCADOCR_WX_BASE_URL=http://39.107.55.179` 正是依赖 IP 直连,
|
||
所以**不能简单删掉 `app.conf`**(删了 IP 访问会落到 nginx.conf 内置的静态
|
||
server,`/auth/`、`/usage/` 全部 404)。要收紧的话,应把 `wechat-api.conf`
|
||
改成 `listen 80 default_server` 并删 `app.conf`,而不是单独删。
|
||
|
||
- 新增对外接口必须**同时**在 `wechat-api.conf` 加 `location` 块(给域名路径),
|
||
否则备案后走域名会落到占位响应。
|
||
- 改 nginx 的流程见 `user_shell_workflow.md`:**写成脚本 + `sudo bash ~/xxx.sh`**,
|
||
不要给内联长命令。
|
||
- **`/docs` 暴露问题用户 2026-09-27 决定暂不处理**(原话选了"暂不处理"):判断是
|
||
风险低(只泄露接口结构、不含数据,`/admin` 另有强密码),等 ICP 备案通过、插件
|
||
切回域名后再一并处理。**不要再自作主张去关 docs 或删 `app.conf`**——要动先问。
|
||
|
||
**`/admin` 已于 2026-09-27 20:13 上线**:`wechat-api.conf` 新增 `location /admin`
|
||
块(显式透传 `Authorization` 头),应用侧 `ADMIN_USER=admin`、密码为 24 位随机串
|
||
存于服务器 `.env`(用户已自行保存)。`/admin` 需重启应用才生效——`ADMIN_PASSWORD`
|
||
是进程启动时读入的模块级常量。
|
||
|
||
4. **GitHub 已弃用,不要再往那儿推**(2026-09-27 用户决定)。服务器到 github.com 的连接
|
||
本就间歇性失败(`git pull` 报 `Empty reply from server` 或超时;`curl -sI` 探到 200
|
||
也**不代表** git 协议可用,实测出现过 curl 200 而 pull 仍失败)。国内访问不稳定,
|
||
用户明确不要 GitHub 备份了。所有代码走自建 Git 服务器。
|
||
若哪天需要从 GitHub 临时取东西(例如查旧仓库),仍可用 bundle 绕:
|
||
本地 `git bundle create /tmp/x.bundle main`,
|
||
`ssh deploy@... 'cat > /tmp/x.bundle' < /tmp/x.bundle`,服务器上
|
||
`git fetch /tmp/x.bundle main && git merge --ff-only FETCH_HEAD`。
|
||
|
||
**Why:** 这些都是服务器实际状态,仓库代码和 git 历史里看不出来;REQUIREMENTS.md 的
|
||
技术栈描述(Docker MySQL)与实际不符,照着文档操作会踩坑。
|
||
|
||
**How to apply:** 涉及服务器操作时,直接用上面的真实路径/账号/镜像;给用户的命令要
|
||
考虑他需要手动 sudo;新增对外路由时提醒同步改 nginx。
|