diff --git a/server/feedback/README.md b/server/feedback/README.md index 8269b8e..420f77f 100644 --- a/server/feedback/README.md +++ b/server/feedback/README.md @@ -1,5 +1,7 @@ # App feedback → Feishu SMTP +For SafeLine owning public ports 80/443 and OpenResty in host mode on 10080/12443, follow [the Chinese SafeLine deployment guide](deployment-safeline-1panel.md) and `openresty-safeline-http.conf.example`. TLS terminates at SafeLine; its origin is OpenResty's HTTP port 10080. + Prepared for Ubuntu + 1Panel on `43.138.57.216`. Public endpoint: `https://app.glowtps.cn/v1/feedback`. Server sender: `authors@glowtps.cn`; fixed receiver: `tps-david@glowtps.cn`. This directory contains deployment source; no connection to the server or live SMTP test has been performed. ## Contract diff --git a/server/feedback/deployment-safeline-1panel.md b/server/feedback/deployment-safeline-1panel.md new file mode 100644 index 0000000..3598e3d --- /dev/null +++ b/server/feedback/deployment-safeline-1panel.md @@ -0,0 +1,152 @@ +# 雷池 WAF + 1Panel:TPSchedule 反馈服务部署 + +适用环境:Ubuntu,雷池占用80/443;OpenResty为host网络模式,HTTP为10080、HTTPS为12443;雷池现有站点通过127.0.0.1回源。以下是按已知环境制定的操作步骤,尚未在你的服务器执行。 + +## 访问链路 + +```text +手机APP:https://app.glowtps.cn/v1/feedback + ↓ HTTPS,公网443 +雷池WAF:证书和外部HTTPS跳转 + ↓ http://127.0.0.1:10080,Host仍为app.glowtps.cn +OpenResty:app.glowtps.cn站点 + ↓ http://127.0.0.1:18080 +反馈服务容器:内部8000 + ↓ smtp.feishu.cn:465,SSL +authors@glowtps.cn → tps-david@glowtps.cn +``` + +该反馈站点在雷池终止HTTPS,后两段是同机内部HTTP。OpenResty已有12443端口及其他网站保持原设置。反馈容器使用现有Compose配置,宿主机仅发布127.0.0.1:18080,公网安全组无需增加该端口。 + +## 1. 上传、解压和确认目录 + +将TPSchedule-feedback-server.zip上传到1Panel文件管理并解压。压缩包内有feedback目录;将这个目录中的文件放到 `/opt/app-feedback`,确保 `/opt/app-feedback/Dockerfile` 和 `/opt/app-feedback/docker-compose.yml` 实际存在。不要形成多套嵌套目录后在错误目录启动。 + +在1Panel服务器终端查看目标端口是否已被占用: + +```sh +ss -ltn '( sport = :18080 )' +``` + +若18080已有其他服务,不要停止它;告诉开发者占用情况后再协调新端口。以下步骤使用18080。 + +## 2. 填写服务器配置 + +在文件管理中将 `.env.example` 复制为 `.env`,保留模板其他参数,核对: + +```dotenv +SMTP_HOST=smtp.feishu.cn +SMTP_PORT=465 +SMTP_TLS=ssl +SMTP_USERNAME=authors@glowtps.cn +MAIL_FROM=authors@glowtps.cn +MAIL_TO=tps-david@glowtps.cn +DATABASE_PATH=/app/data/feedback.sqlite3 +``` + +SMTP_PASSWORD填写飞书邮箱的第三方客户端授权密码,只在服务器编辑器里填写。IDEMPOTENCY_SECRET填写持久随机密钥,在服务器终端生成: + +```sh +openssl rand -hex 32 +``` + +将生成结果填回 `.env` 中的IDEMPOTENCY_SECRET。两个示例REPLACE_占位值均须替换,秘密值建议用英文单引号包住。不要将真实密码或密钥发到聊天里。 + +## 3. 启动服务并验证第一段 + +在1Panel服务器终端执行: + +```sh +cd /opt/app-feedback +chmod 600 .env +docker compose up -d --build +docker compose ps +curl --fail --max-time 10 http://127.0.0.1:18080/healthz +``` + +最后返回 `{"status":"ok"}` 才继续。这一步不发送邮件,也不验证飞书密码;它验证容器和本机18080。首次构建需要服务器能下载镜像及Python依赖。启动失败可以查看 `docker compose logs --tail 50 feedback`,分享错误时不要附带.env或完整容器环境详情。 + +可以改用1Panel“容器→编排”启动,但构建目录须为上述目录,环境文件也在该目录;固定使用同一个Compose项目 `app-feedback`,避免创建多个副本。 + +## 4. 配置OpenResty的10080站点 + +在1Panel“网站”中找到或新建 `app.glowtps.cn` 站点。新建可选择反向代理,代理地址填 `http://127.0.0.1:18080`。网站域名绑定app.glowtps.cn,HTTP监听10080;如果主域名字段要求域名加端口,填 `app.glowtps.cn:10080`。最终配置应能看到 `listen 10080;` 和 `server_name app.glowtps.cn;`,以实际生成配置为准。 + +这个内部HTTP站点关闭“强制HTTPS/HTTP转HTTPS”。外部HTTPS由雷池负责,否则雷池每次HTTP回源都可能再次触发HTTPS重定向。无需给这个新反馈站点另开12443。 + +先保存该站点原配置。将同目录 `openresty-safeline-http.conf.example` 中的配置加入此站点的HTTP `server { ... }` 内。不要替换OpenResty总配置。若已经有同名精确location,修改原块,保持每个路径只有一个对应块;域名已有页面时保留其他路由。 + +该片段只信任127.0.0.1/::1传来的真实IP信息。雷池通过X-Forwarded-For透传源IP,经OpenResty校正后再发送给服务,以免所有学生被当成同一个IP、共享每小时10次的限制。不要将可信来源设置成0.0.0.0/0。若现有配置也在此层处理真实IP,先核对并合并,避免重复指令;如果雷池实际连接源地址不是回环地址,应仅补充那个确定的可信地址。 + +在1Panel执行配置检查,检查成功后重新加载OpenResty。然后在宿主机终端验证: + +```sh +curl --fail --max-time 10 -H 'Host: app.glowtps.cn' http://127.0.0.1:10080/healthz +``` + +同样应返回 `{"status":"ok"}`。到这里已验证服务和OpenResty,雷池尚未参与。 + +## 5. 配置雷池站点 + +在雷池中找到或添加app.glowtps.cn防护站点,按以下设置: + +| 配置项 | 值 | +|---|---| +| 域名 | app.glowtps.cn | +| 公网HTTP端口 | 80,保持现有入口 | +| 公网HTTPS端口 | 443 | +| 上游服务器 | http://127.0.0.1:10080 | +| 回源Host | app.glowtps.cn,保留原Host或明确填写 | +| HTTPS证书 | 能覆盖app.glowtps.cn的有效证书 | +| 外部HTTP跳转HTTPS | 在雷池设置 | + +如果上游地址UI将协议/地址/端口拆开,分别选择HTTP、127.0.0.1、10080。保持路径 `/v1/feedback` 原样转发,不额外增加前缀;上游不要填公网上的app.glowtps.cn,以免请求转回雷池形成循环。证书可使用已有覆盖该域名的通配证书;申请新证书时可选DNS验证,避免绕行到内部站点的HTTP校验问题。 + +域名A记录应指向43.138.57.216;已有正确记录时沿用。所有公网入口继续经过雷池。 + +该接口来自原生APP HTTP客户端,无法执行网页JavaScript或拖动验证码。对这个API站点或精确路径 `/v1/feedback`、`/healthz` 避免开启浏览器人机验证、网页登录跳转、动态JS注入和等待室。根据版本提供的站点/路径配置范围操作,保留适用于API的攻击检测及频率限制;不要通过全站攻击白名单解决误拦。 + +图片、视频请求使用multipart/form-data。雷池此站点应允许约21MiB的HTTP请求体,回源等待时间设为至少60秒(如支持,可与OpenResty设置一致为180秒)。服务自己仍限制最多3个附件、单个8MiB、合计20MiB。雷池HTTP请求体限额不足时会在服务之前返回413;浏览器挑战可能返回HTML甚至200,APP会拒绝把它当作提交成功。 + +## 6. 验证整条链路及邮件 + +在服务器执行,或用手机浏览器访问这个完整健康检查地址: + +```sh +curl --fail --max-time 15 https://app.glowtps.cn/healthz +``` + +应直接返回 `{"status":"ok"}`。curl默认校验证书,正常验证时不加 `-k`。根地址 `/` 不是健康检查页面,出现404不能据此判断反馈服务失败。 + +然后从TPSchedule的“我的→反馈与共建”提交一条“部署测试”,检查 `tps-david@glowtps.cn` 是否收到。若要同时验证重复提交保护,在服务器终端手动发送两次完全相同的测试请求: + +```sh +feedback_test_id=$(openssl rand -hex 16) +curl --max-time 30 -i https://app.glowtps.cn/v1/feedback \ + -F "submission_id=$feedback_test_id" \ + -F 'type=problem' \ + -F 'message=TPSchedule 部署测试,请忽略。' +``` + +保留feedback_test_id,在同一终端再次执行这条curl(不重新生成ID)。两次应确认相同ID的sent结果,邮箱只收到一封。success:true/status:sent表示SMTP接受并记入数据库,实际进入收件箱需你核对。 + +## 分层排查 + +| 哪一段失败 | 优先检查 | +|---|---| +| 本机18080/healthz失败 | Compose状态、构建、.env必填配置、容器日志 | +| 18080正常,10080加Host失败 | 站点监听10080、域名匹配、精确location、代理地址 | +| 两个本机地址正常,公网失败 | DNS、雷池证书/域名/上游/Host、人机验证、WAF记录 | +| healthz正常,POST被拦或返回HTML | 该接口的雷池挑战、攻击拦截、上传限制 | +| POST返回smtp_unavailable | 飞书客户端授权、账号、服务器出站465、发信权限 | +| POST返回429 | 测试频率、真实IP链路;服务默认每IP每小时10次/全局100次 | +| 返回delivery_uncertain | 先查邮件及投递状态,用原ID核对,不生成新ID强行重发 | + +更新时保持.env、原IDEMPOTENCY_SECRET和SQLite数据卷,继续同一Compose项目。不要执行 `docker compose down -v`,它会移除重复提交记录。 + +## 官方参考 + +- [1Panel创建反向代理网站](https://docs.fit2cloud.com/1panel/user_manual/websites/website-create/) +- [雷池常见问题:上游源IP透传](https://help.waf-ce.chaitin.cn/node/01975855-cd7d-78fb-a354-d2aa65a53cce) +- [雷池人机验证使用浏览器环境](https://help.waf-ce.chaitin.cn/node/01973fc6-e1d8-7af4-b697-ab1312b8f9bd) +- [Nginx真实IP模块及可信来源](https://nginx.org/en/docs/http/ngx_http_realip_module.html) diff --git a/server/feedback/openresty-safeline-http.conf.example b/server/feedback/openresty-safeline-http.conf.example new file mode 100644 index 0000000..a9876db --- /dev/null +++ b/server/feedback/openresty-safeline-http.conf.example @@ -0,0 +1,36 @@ +# Insert inside app.glowtps.cn's HTTP server block managed by 1Panel. +# That server should listen on 10080; HTTPS terminates at SafeLine on 443. +# Do not enable an HTTP-to-HTTPS redirect on this internal server. +# Assumption: SafeLine connects from loopback, with a correct X-Forwarded-For. +set_real_ip_from 127.0.0.1; +set_real_ip_from ::1; +real_ip_header X-Forwarded-For; +real_ip_recursive on; + +location = /v1/feedback { + client_max_body_size 21m; + client_body_timeout 60s; + proxy_request_buffering off; + proxy_buffering off; + proxy_http_version 1.1; + proxy_pass http://127.0.0.1:18080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header Connection ""; + proxy_connect_timeout 5s; + proxy_send_timeout 180s; + proxy_read_timeout 180s; + proxy_next_upstream off; + access_log off; +} + +location = /healthz { + proxy_pass http://127.0.0.1:18080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto https; + access_log off; +} diff --git a/server/feedback/部署说明.md b/server/feedback/部署说明.md index ef1ea56..879ac45 100644 --- a/server/feedback/部署说明.md +++ b/server/feedback/部署说明.md @@ -1,5 +1,7 @@ # 反馈邮件服务:1Panel 部署说明 +如果80/443由雷池WAF占用,且OpenResty为host模式、端口10080/12443,请优先使用[雷池专用部署指南](deployment-safeline-1panel.md)和`openresty-safeline-http.conf.example`,HTTPS在雷池处理。 + 已准备好服务文件,尚未连接服务器、部署或发送真实邮件。 | 项目 | 配置 |