From 23799b4ea50d815341639f1d34c5aa4ad2ade155 Mon Sep 17 00:00:00 2001 From: wangchuanli Date: Mon, 14 Sep 2026 16:15:26 +0800 Subject: [PATCH] =?UTF-8?q?feat(oss):=20=E8=A1=A5=E9=BD=90=E5=BC=80?= =?UTF-8?q?=E6=BA=90=E5=A3=B0=E6=98=8E=E4=BD=93=E7=B3=BB=EF=BC=88MIT=20+?= =?UTF-8?q?=20=E7=AC=AC=E4=B8=89=E6=96=B9=E5=A3=B0=E6=98=8E=20+=20?= =?UTF-8?q?=E8=B4=A1=E7=8C=AE/=E5=AE=89=E5=85=A8/=E8=A1=8C=E4=B8=BA?= =?UTF-8?q?=E5=87=86=E5=88=99=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * LICENSE —— MIT * THIRD-PARTY-NOTICES —— 依赖清单、再分发合规说明(含随仓库分发的 Apache ECharts 5.6.0 / Apache-2.0)与自查清单 * CONTRIBUTING.md —— 开发环境、五层验证、必须遵守的不变量、提交规范 * SECURITY.md —— 漏洞私有报告渠道、已有措施、已知非目标 * CODE_OF_CONDUCT.md —— 改编自 Contributor Covenant 2.1 * .github/ —— Bug 报告 / 功能建议表单 + PR 模板 * .editorconfig —— 与 .gitattributes 保持一致 * 全部 Python / Shell 源文件加 SPDX-License-Identifier: MIT 头 * README 增加「开源与许可」章节与许可标识 --- .editorconfig | 33 ++++++ .github/ISSUE_TEMPLATE/bug_report.yml | 83 +++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 8 ++ .github/ISSUE_TEMPLATE/feature_request.yml | 47 +++++++++ .github/PULL_REQUEST_TEMPLATE.md | 46 +++++++++ CODE_OF_CONDUCT.md | 59 +++++++++++ CONTRIBUTING.md | 112 +++++++++++++++++++++ LICENSE | 21 ++++ README.md | 47 ++++++++- SECURITY.md | 67 ++++++++++++ THIRD-PARTY-NOTICES.md | 78 ++++++++++++++ docker/entrypoint.sh | 3 + docker/healthcheck.py | 3 + docs/CHANGELOG.md | 17 +++- manage.py | 3 + tools/check_live.py | 5 +- tools/push-all.sh | 3 + tools/shots.py | 3 + tools/smoke.py | 3 + workbuddy_portal/__init__.py | 3 + workbuddy_portal/client.py | 3 + workbuddy_portal/collect.py | 3 + workbuddy_portal/config.py | 3 + workbuddy_portal/db.py | 3 + workbuddy_portal/query.py | 3 + workbuddy_portal/scheduler.py | 3 + workbuddy_portal/security.py | 3 + workbuddy_portal/web/__init__.py | 3 + workbuddy_portal/web/api.py | 3 + workbuddy_portal/web/views.py | 3 + 30 files changed, 671 insertions(+), 3 deletions(-) create mode 100644 .editorconfig create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 SECURITY.md create mode 100644 THIRD-PARTY-NOTICES.md diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9b642ff --- /dev/null +++ b/.editorconfig @@ -0,0 +1,33 @@ +# 统一的编辑器约定。与 .gitattributes(* text=auto eol=lf)保持一致。 +# 这样无论谁在什么系统上编辑,提交进来的都是 LF 与 UTF-8。 +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +[*.{html,css,js,json,yml,yaml,svg}] +indent_size = 2 + +[*.py] +indent_size = 4 +max_line_length = 100 + +[*.md] +# Markdown 里行尾两个空格是有意义的分行,别自动删掉 +trim_trailing_whitespace = false + +[*.sh] +indent_size = 4 +# 容器 entrypoint 必须是 LF,否则报 exec format error / no such file or directory +end_of_line = lf + +[Makefile] +indent_style = tab + +[*.{png,jpg,jpeg,gif,ico,woff,woff2,sqlite}] +insert_final_newline = false diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..aee73f3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,83 @@ +name: Bug 报告 +description: 报告一个可复现的问题 +title: "[Bug] " +labels: [bug] +body: + - type: markdown + attributes: + value: | + 感谢反馈。提交前请先确认: + + 1. 你已经读过 [FAQ](https://git.iwali.top/wangchuanli/workbuddy-portal/src/branch/main/docs/FAQ.md) 与 [部署排错](https://git.iwali.top/wangchuanli/workbuddy-portal/src/branch/main/docs/DEPLOYMENT.md); + 2. **不要贴真实 Cookie、secret_key 或真实用量数据**——需要复现请用 `python tools/demo_data.py` 生成的示例数据。 + + - type: input + id: version + attributes: + label: 版本 / 提交号 + description: 例如 1.1.0,或 `git rev-parse --short HEAD` 的输出 + placeholder: 1.1.0 / 118e27f + validations: + required: true + + - type: dropdown + id: deploy + attributes: + label: 部署方式 + options: + - Docker Compose(命名卷,推荐) + - Docker Compose + hostdir 叠加层(绑定挂载) + - 裸机 waitress + - 裸机 Flask 开发服务器 + - 其他(请在补充说明里写) + validations: + required: true + + - type: input + id: env + attributes: + label: 运行环境 + description: 操作系统与 Python 版本 + placeholder: Windows 11 + Docker Desktop 4.3x / Ubuntu 24.04 + Python 3.13 + validations: + required: true + + - type: textarea + id: what + attributes: + label: 现象 + description: 发生了什么?预期是什么? + validations: + required: true + + - type: textarea + id: repro + attributes: + label: 复现步骤 + description: 越具体越好。涉及数据口径的问题请说明用的是示例数据还是真实数据。 + placeholder: | + 1. `docker compose up -d --build` + 2. 打开 /dashboard + 3. 点击「30 天」 + 4. 看到 … + validations: + required: true + + - type: textarea + id: logs + attributes: + label: 日志 / 报错 + description: | + 相关日志。**请先脱敏**(抹掉 Cookie、域名、内网 IP、真实模型名)。 + 大屏类问题请附浏览器 Console 的报错。 + render: text + validations: + required: false + + - type: textarea + id: checks + attributes: + label: 已做过的自查 + description: 例如是否跑过 `tools/check_live.py`、是否换过浏览器、是否重启过容器 + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..6dcd55b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: 使用问题 / 部署排错 + url: https://git.iwali.top/wangchuanli/workbuddy-portal/src/branch/main/docs/FAQ.md + about: 常见问题、Cookie 获取、时区与调度、备份恢复等,先看 FAQ 与部署文档 + - name: 安全漏洞 + url: https://git.iwali.top/wangchuanli/workbuddy-portal/src/branch/main/SECURITY.md + about: 请勿公开提交可直接利用的漏洞细节,按 SECURITY.md 走私有渠道 diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..e6320b3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,47 @@ +name: 功能建议 +description: 提出一个新功能或改进 +title: "[Feature] " +labels: [enhancement] +body: + - type: markdown + attributes: + value: | + 在提建议前,请先看一眼 [架构说明](https://git.iwali.top/wangchuanli/workbuddy-portal/src/branch/main/docs/ARCHITECTURE.md) + 里的「已知边界」——有些能力是**刻意不做**的(例如不引入 APScheduler、不新增第三方依赖)。 + + - type: textarea + id: problem + attributes: + label: 你想解决什么问题 + description: 先说场景与痛点,再说方案。这样更容易判断有没有更简单的做法。 + placeholder: 我在做 … 的时候,必须手动 … ,很费时。 + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: 你期望的做法 + description: 如果有具体的接口/页面/参数设计,写在这里 + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: 考虑过的替代方案 + description: 以及为什么它们不够好 + validations: + required: false + + - type: checkboxes + id: constraints + attributes: + label: 约束自查 + options: + - label: 该功能不需要新增第三方依赖(或已在下方说明理由) + required: false + - label: 该功能不破坏「SQLite 单写者」这一前提 + required: false + - label: 若涉及列表类接口,我不会在其中返回 `prompt` 全文 + required: false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..271263f --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,46 @@ +## 这个 PR 做了什么 + + + +## 关联 Issue + + + +## 改动类型 + +- [ ] Bug 修复 +- [ ] 新功能 +- [ ] 重构(不改变外部行为) +- [ ] 文档 +- [ ] 构建 / 部署 / CI + +## 改动清单 + + + +## 验证情况 + + + +| 检查 | 结果 | +|---|---| +| `python -m compileall -q workbuddy_portal manage.py tools` | | +| `python tools/smoke.py` | `RESULT: ok=?? fail=0` | +| `python tools/check_live.py`(如起了服务) | `RESULT: ok=?? fail=0` | +| `python tools/shots.py`(如改了前端) | 无 JS 报错 | +| `docker compose up -d --build`(如改了容器相关) | 容器 healthy | + +## 破坏性变更 / 需要部署方做的事 + + + +## 自查确认 + +- [ ] 没有提交任何凭据、`data/usage.sqlite`、`logs/*`、`instance.json`(已用 `git check-ignore -v` 复核过忽略规则) +- [ ] 没有在列表类接口里新增返回 `prompt` 全文 +- [ ] 若涉及数据口径变更,我已用**独立聚合**与页面结果逐项比对 +- [ ] 新增的第三方资源已登记到 `THIRD-PARTY-NOTICES.md` +- [ ] 文档(README / docs / CHANGELOG)已同步更新 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..c40922f --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,59 @@ +# 行为准则(Code of Conduct) + +## 我们的承诺 + +为了营造开放、友善的协作环境,我们作为贡献者与维护者承诺:**让每个人参与本项目的体验都不受骚扰**, +无论其年龄、体型、可见或不可见的残障、族裔、性别认同与表达、经验水平、教育程度、 +社会经济状况、国籍、外貌、种族、宗教,或性取向与身份认同。 + +## 我们的标准 + +**有助于营造积极环境的行为:** + +- 对他人展现同理心与善意 +- 尊重不同的意见、观点与经验 +- 给出并优雅地接受建设性的反馈 +- 承担责任、向被我们影响的人致歉,并从中学习 +- 关注对整个社区最有利的事,而不只是对我们个人 + +**不可接受的行为:** + +- 性化的言语或图像,以及任何形式的性关注或挑逗 +- 挑衅、侮辱或贬损性评论,以及人身攻击或政治攻击 +- 公开或私下的骚扰 +- 未经明确许可,公布他人的私人信息(如真实姓名、住址、邮箱、凭据等) +- 其他在专业场合中可被合理视为不当的行为 + +> 与本项目技术定位直接相关的一条:**请勿在 Issue、PR、讨论或截图中提交真实的账号凭据、 +> 云端 Cookie、`secret_key` 或真实用户的用量数据**。需要复现时请用 `tools/demo_data.py` +> 生成的示例数据,或自行脱敏。这类内容会被立即删除。 + +## 维护者的责任 + +维护者负责澄清并执行上述标准,对任何被视为不当、威胁、冒犯或有害的行为, +有权采取适当且公平的纠正措施,包括删除、编辑或拒绝评论、提交、代码与 Issue, +并在必要时临时或永久禁止任何贡献者参与。 + +## 适用范围 + +本准则适用于所有项目空间,也适用于个人在**公开场合代表本项目**时的言行。 + +## 报告与执行 + +如遇滥用、骚扰或其他不可接受的行为,请通过维护者在代码托管平台上公布的联系方式私下报告 +(参见 [SECURITY.md](SECURITY.md) 中的私有渠道)。所有投诉都会被及时、公正地审查与处理。 +维护者有义务尊重报告者的隐私与安全。 + +## 执行准则 + +维护者将按下述梯度决定后果: + +1. **更正** — 私下书面警告,说明违规性质并解释为何不当。必要时要求公开致歉。 +2. **警告** — 在一段时间内禁止与相关人员互动;违反将导致临时或永久封禁。 +3. **临时封禁** — 在指定期限内禁止任何形式的公开或私下互动。 +4. **永久封禁** — 永久禁止在项目内进行任何形式的公开互动。 + +## 致谢 + +本准则改编自 [Contributor Covenant](https://www.contributor-covenant.org/) 2.1 版 +(原文以 CC BY 4.0 发布)。执行准则参考其「Enforcement Guidelines」部分。 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e5224ef --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,112 @@ +# 贡献指南(Contributing) + +感谢你有兴趣改进 WorkBuddy Portal。这是一个**单进程 Flask + SQLite** 的轻量项目, +刻意保持了很小的依赖面与很平的目录结构——请先花两分钟读完本文,你的改动会更顺利被接受。 + +--- + +## 一、开发环境 + +| 项 | 要求 | +|---|---| +| Python | **3.13**(Docker 镜像用的就是 3.13-slim;3.11+ 一般也可) | +| 操作系统 | Linux / macOS / Windows 均可,但**注意**下面「Windows 特有坑」一节 | +| 依赖 | `pip install -r requirements.txt` | +| 可选 | Playwright + Chromium(只有跑界面截图才需要) | + +起步: + +```bash +git clone <你的仓库地址> workbuddy-portal +cd workbuddy-portal +python -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate +pip install -r requirements.txt + +python manage.py init # 建表 + 建管理员(默认 admin/admin123) +python manage.py serve --port 8848 # 或 docker compose up -d --build +``` + +## 二、用示例数据开发,别用真实数据 + +`tools/demo_data.py` 会生成一份**完全合成**的数据集(假模型名、假 Prompt、偏斜的积分分布), +放在 `data/demo/` 下(该目录已在 `.gitignore` 内): + +```bash +python tools/demo_data.py # 默认 data/demo,管理员 admin/admin123 +WB_DATA_DIR=$PWD/data/demo python manage.py serve --port 8849 --no-scheduler +``` + +这样你既能有一个「看起来像真的」的界面来调试,也不会把任何真实用量带进仓库或截图。 + +## 三、改动前请先跑一遍验证 + +项目有一层层递进的验证,**代价从低到高**,改完至少跑到第 2 层: + +| # | 命令 | 覆盖什么 | 需要什么 | +|---|---|---|---| +| 1 | `python -m compileall -q workbuddy_portal manage.py tools` | 语法 | — | +| 2 | `python tools/smoke.py` | **99 项**离线断言:全页面只读渲染、模板残留检测、历史缺陷防回归、静态资源、CSS 类名对账 | 无(用 Flask test_client,不启服务) | +| 3 | `python tools/check_live.py --base http://127.0.0.1:8848` | **56 项**真实 HTTP 断言,含登录/CSRF/开放重定向 | 一个运行中的服务 | +| 4 | `python tools/shots.py --base http://127.0.0.1:8849` | 登录后逐页截图并收集 `console`/`pageerror` | Playwright + Chromium | +| 5 | `docker compose up -d --build && docker compose ps` | 容器化路径 | Docker | + +> `tools/shots.py` 是**最有价值的一层**:项目曾经出过「大屏整页全白」的 bug, +> 只有它抓到了(`smoke` 与 `check_live` 都放过了)。改前端务必跑。 + +这三支脚本都会打印 `RESULT: ok=N fail=0`,`fail` 不为 0 时退出码是 1,可直接接进 CI。 + +## 四、必须遵守的几条不变量 + +这些是踩过坑之后定下来的,破坏它们会在生产上以很隐蔽的方式出问题: + +1. **SQLite 只允许一个写者。** 任何采集动作都要走 `collect._Lock()`(`data/collect.lock`)。 + 不要起多个带调度的进程;多实例部署时其余实例设 `WB_DISABLE_SCHEDULER=1`。 +2. **列表类接口默认不返回 `prompt` 全文。** 它占原始体积约 80%。只有 `/api/top` 与 + `/api/records` 带,且都做截断。新增接口请沿用这个约定。 +3. **两种字段命名契约不要互相「统一」**:`/api/bundle` 用短键(`d/c/k/m/cl/t/px`,大屏页依赖), + `/api/records` 用可读全名。改错会让大屏静默渲染成空白。 +4. **流式响应里不要复用 `db.get_db()`。** Flask 在响应迭代开始前就会关掉请求上下文里的连接, + 生成器一读库就 `Cannot operate on a closed database`。要在生成器内部自建连接并 `finally` 关闭。 +5. **Docker 部署用命名卷,不要退回绑定挂载。** Windows + Docker Desktop 走 9p, + 宿主进程碰过 WAL 库之后容器会永久打不开数据库(详见 [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md))。 +6. **`.gitignore` 不支持行尾注释**——规则后跟 `# 注释` 会让整行失效。注释必须单独占一行, + 改完用 `git check-ignore -v ` 逐条确认命中。 + +## 五、代码风格 + +- 遵循 PEP 8;行宽 100。 +- **注释与文档字符串用中文**,说明「为什么这么做」而不是「这行在做什么」。 +- 提交前用 `python -m compileall` 与 `python tools/smoke.py` 自查。 +- 不要引入新的第三方依赖,除非有充分理由并在 PR 里说明——这个项目的卖点之一就是依赖少。 + 如果确实新增了,请同步登记到 [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md)。 + +## 六、提交与 PR + +提交信息用 **Conventional Commits**,一句话说清「改了什么」即可,正文可写动机: + +``` +fix(docker): 数据改用 Docker 命名卷,修容器打不开数据库的问题 +feat(api): 新增 /api/export 支持按筛选条件导出 CSV +docs: 补充反向代理部署示例 +``` + +PR 请包含: + +- **动机**:解决什么问题,或复现步骤 +- **改动**:涉及哪些文件、有没有破坏性变更 +- **验证**:贴出 `smoke` / `check_live` 的 `RESULT` 行;改前端再贴截图脚本的输出 +- 若改动影响数据口径(聚合、去重、时区),请**额外写一份独立聚合与之比对**, + 不要只靠肉眼看页面 + +## 七、Windows 特有坑(若你在 Windows 上开发) + +- 宿主 Windows 进程访问过 `data/usage.sqlite` 后,**容器内**会打不开同一个库。 + 用命名卷部署时,宿主侧跑 CLI 请一律走 `docker compose exec portal python manage.py …`。 +- Git 的 `/tmp` 会被解析成 `C:\tmp`,`git commit -F /tmp/msg.txt` 会失败——用仓库内路径。 +- 本机若开着 HTTP 代理,`curl http://127.0.0.1:…` 会被代理拦成 502,探测本地服务要加 `--noproxy '*'`。 +- 行尾:仓库用 `.gitattributes` 锁死 `eol=lf`,`docker/*.sh` 若带 CRLF 会在容器里报 + `exec format error`。 + +--- + +有任何不确定的地方,先在 Issue 里问,比写完再返工更省事。 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..76c6931 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Wang Chuanli + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index c45cdfe..ef7a783 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,10 @@ | 部署 | Docker Compose / 裸机 `waitress`;镜像可推 Gitea 容器注册表 | | 鉴权 | 全站登录 + CSRF + 角色(管理员 / 普通用户),凭证存库、页面只回掩码 | | 版本 | v1.1.0 | +| **许可证** | **MIT**(第三方组件与再分发资源见 [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md)) | **目录**:[核心特性](#核心特性) · [架构](#架构一图) · [快速开始](#快速开始) · [命令一览](#命令一览) · -[页面一览](#页面一览) · [接口一览](#接口一览) · [文档导航](#文档导航) · [安全须知](#安全须知) +[页面一览](#页面一览) · [接口一览](#接口一览) · [文档导航](#文档导航) · [安全须知](#安全须知) · [开源与许可](#开源与许可) --- @@ -245,6 +246,11 @@ workbuddy-portal/ | [docs/API.md](docs/API.md) | 开发 / 集成 | 接口参考:路径、参数、返回结构、错误码 | | [docs/FAQ.md](docs/FAQ.md) | 所有人 | 常见问题:采集为空、Cookie 失效、时区、性能、权限 | | [docs/CHANGELOG.md](docs/CHANGELOG.md) | 所有人 | 变更日志 | +| [CONTRIBUTING.md](CONTRIBUTING.md) | 贡献者 | 贡献指南:开发环境、验证分层、必须遵守的不变量、提交规范 | +| [SECURITY.md](SECURITY.md) | 运维 / 安全 | 安全策略:漏洞私有报告渠道、已有措施、已知非目标 | +| [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md) | 合规 | 第三方组件清单与许可证(含随仓库再分发的 ECharts) | +| [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) | 所有人 | 行为准则 | +| [LICENSE](LICENSE) | 所有人 | MIT 许可证全文 | --- @@ -259,3 +265,42 @@ workbuddy-portal/ - **开放重定向防护**:登录跳转的 `next` 只接受站内相对路径,`//evil.com` 这类协议相对 URL 一律回落到 `/`。 - **登录限速**:同 IP 连续失败 5 次锁定 10 分钟;失败计数表有上限与 TTL。 - **不进版本库的文件**:`data/instance.json`(含 `secret_key`)、`data/usage.sqlite`、`logs/`、`.env`(含明文密码)。 + +--- + +## 开源与许可 + +本项目以 **MIT 许可证**发布,全文见 [LICENSE](LICENSE)。你可以自由使用、修改、商用与再分发, +只需保留版权声明与许可声明。 + +### 第三方组件(务必看一眼) + +用量大屏**随仓库再分发了 Apache ECharts 5.6.0**(`workbuddy_portal/web/static/dashboard/vendor/echarts.min.js`, +Apache-2.0 许可)——之所以内置而不走 CDN,是为了让大屏在局域网内离线可用。 +按 Apache-2.0 第 4 条,再分发时需保留其许可证与版权声明(该文件头部已自带)。 +其余运行期依赖(Flask / waitress / openpyxl)不在本仓库内,由使用方安装时获取。 + +完整的依赖清单、许可证对照表与**合规自查清单**见 [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md)。 + +### 参与贡献 + +- 想改代码?先读 [CONTRIBUTING.md](CONTRIBUTING.md) —— 里面有**必须遵守的几条不变量** + (SQLite 单写者、列表接口不回 `prompt` 全文、两种字段命名契约不要互相「统一」……), + 以及从 `compileall` 到容器验证的五层自检该怎么跑。 +- 有想法但手上没有真实数据?`python tools/demo_data.py` 会生成一份**完全合成**的示例库, + 写到 `data/demo/`(已在 `.gitignore` 内),可直接拿来调试界面与截图。 +- 发现安全漏洞?**请不要开公开 Issue**,按 [SECURITY.md](SECURITY.md) 走私有渠道。 +- 参与本项目即表示你同意遵守 [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)。 + +### 文档里的数据都是合成的 + +`docs/images/` 的全部界面截图与 `docs/` 中的 JSON 示例**均为合成数据**: +模型名统一为 `demo-*`,客户端为 `vscode` / `webconsole` / `sdk`,Prompt 为通用示例文本, +审计 IP 取自 RFC 5737 的文档专用网段(`192.0.2.0/24`)。 +生成方式是 `tools/demo_data.py`,所以任何人不需要真实账号就能复现整套文档。 + +### 致谢 + +- 交互大屏依赖 [Apache ECharts](https://echarts.apache.org/) +- Web 框架 [Flask](https://flask.palletsprojects.com/),生产服务器 [waitress](https://github.com/Pylons/waitress) +- 行为准则框架来自 [Contributor Covenant](https://www.contributor-covenant.org/) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..390c76c --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,67 @@ +# 安全策略(Security Policy) + +## 支持范围 + +本项目按「自托管、局域网内使用」的定位开发。安全修复只针对当前主分支与最新发布版本。 + +| 版本 | 是否接受安全修复 | +|---|---| +| `1.1.x`(当前) | ✅ | +| `< 1.1` | ❌ 请先升级 | + +## 如何报告漏洞 + +**请不要在公开 Issue 里贴出可直接利用的细节**(含真实 Cookie、`secret_key`、可复现的绕过步骤)。 + +请通过以下任一私有渠道联系维护者: + + +- 邮件:`<安全联系邮箱>`(占位,待维护者补全) +- 或通过代码托管平台(Gitea)的站内私信联系仓库管理员 + +请在报告里尽量包含: + +1. 受影响的版本 / 提交号 +2. 复现步骤与最小复现(可脱敏) +3. 影响范围(能读到什么、能改到什么) +4. 如果有,你建议的修复方向 + +我们会在 **7 天内**确认收到,并在修复发布后于 CHANGELOG 里致谢(除非你希望匿名)。 + +## 设计上已有的安全措施 + +理解这些边界,有助于你判断某个现象是「设计如此」还是「真的漏洞」: + +| 项 | 做法 | 位置 | +|---|---|---| +| 全站鉴权 | 每个页面都有 `@login_required`,每个 `/api/*` 未登录返回 401 JSON | `workbuddy_portal/security.py`、`web/views.py` | +| CSRF | 所有写请求必须带 `X-CSRF-Token`,页面注入 `window.WB_CSRF`,服务端统一拦截 | `security.check_csrf` | +| 会话签名 | Flask `secret_key` 由 `data/instance.json` 持有,首次启动随机生成 | `workbuddy_portal/config.py` | +| 口令存储 | 加盐哈希,不存明文 | `security.hash_password` | +| 凭据脱敏 | 云端 **Cookie 是账号凭证**:入库后页面与接口**都不回传全文**,只给「N 字符,结尾 …xxxx」 | `config.SECRET_KEYS`、`web/views.py` | +| 开放重定向 | 登录后的 `next` 只允许站内相对路径 | `web/views.py` | +| TLS 校验 | 默认开启,**不提供「关掉校验」的快捷开关**(Cookie 不该裸奔) | `settings.ssl_verify` | +| 容器权限 | 运行层非 root(uid/gid 1000 `app`) | `Dockerfile` | + +**绝不入库**:`data/instance.json`(含 `secret_key`)、`data/usage.sqlite`、`logs/*`、`.env`。 +`.gitignore` 已覆盖;改动忽略规则后请用 `git check-ignore -v ` 逐条复核。 + +## 已知的**非**目标(部署方需自行处理) + +本项目刻意不做下面这些,请按你的环境补齐: + +- **没有多租户与细粒度权限**:登录用户即为管理员,能改配置、能看全部数据。 +- **没有限流与账号锁定**:公网暴露前请置于反向代理的 rate limit 之后。 +- **没有强制 HTTPS**:请由反向代理(nginx/Caddy)终止 TLS。 +- **没有备份机制**:备份策略需要你自己定(见 `docs/DEPLOYMENT.md`)。 +- **不建议直接暴露到公网**:设计前提是局域网或 VPN 内使用。 +- **Cookie 的获取方式由使用者负责**:手动从浏览器复制。请勿把该 Cookie 分享给他人, + 它的权限等同于你的账号;轮换后记得在「配置管理」页更新。 + +## 部署前的最小检查清单 + +- [ ] 已修改默认管理员口令(`WB_ADMIN_PASSWORD`),不再是 `admin123` +- [ ] `data/` 与 `logs/` 目录的权限只对服务账号可读写 +- [ ] 前面有反向代理并启用了 HTTPS +- [ ] 确认 `data/instance.json` 没有被提交到任何仓库 +- [ ] 已规划备份(SQLite 库是唯一正本) diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md new file mode 100644 index 0000000..4d0391b --- /dev/null +++ b/THIRD-PARTY-NOTICES.md @@ -0,0 +1,78 @@ +# 第三方组件与许可声明(Third-Party Notices) + +本项目(WorkBuddy Portal)自身以 [MIT 许可证](LICENSE) 发布。 +但它**依赖**、并在个别位置**再分发**了若干第三方组件。这些组件的著作权归各自作者所有, +其许可条款独立于本项目的 MIT 条款。本文件汇总这些依赖,供合规审查与二次分发时参考。 + +--- + +## 一、运行期依赖(`requirements.txt`) + +这些包不在本仓库内,由使用方安装时获取。 + +| 组件 | 版本要求 | 许可证 | 用途 | +|---|---|---|---| +| [Flask](https://flask.palletsprojects.com/) | `>=3.0` | BSD-3-Clause | Web 框架(路由、Jinja 模板、会话) | +| [waitress](https://github.com/Pylons/waitress) | `>=3.0` | ZPL-2.1 | 生产级纯 Python WSGI 服务器 | +| [openpyxl](https://openpyxl.readthedocs.io/) | `>=3.1` | MIT | 仅 `manage.py import-xlsx` 读 Excel | + +Python 标准库(`sqlite3`、`urllib`、`http`、`threading` 等)按 PSF-2.0 许可,随 Python 分发。 + +> 项目**刻意不依赖** `APScheduler`(调度自实现)与 `requests`(用标准库 `urllib`), +> 因此这两者的许可证与本项目无关。 + +## 二、随仓库再分发的第三方资源 + +这是需要特别注意的一类:文件**物理存在于本仓库中**。 + +### Apache ECharts 5.6.0 — Apache License 2.0 + +- **位置**:`workbuddy_portal/web/static/dashboard/vendor/echarts.min.js` +- **著作权**:Copyright © 2017-2025 Apache Software Foundation 及 ECharts 贡献者 +- **许可证**:Apache License, Version 2.0(全文见 ) +- **为何内置**:用量大屏要在局域网内离线可用,不能依赖公网 CDN +- **未修改**:文件按官方发行版原样保留,其头部已包含 Apache 许可证声明与版权信息 + +按 Apache-2.0 第 4 条要求,再分发时需保留许可证与版权声明——该 `.min.js` 文件头部已自带, +本声明构成附加的显著声明。 + +> **替换说明**:如需升级,从 下载对应版本覆盖同名文件即可, +> 大屏页通过 `/static/dashboard/vendor/echarts.min.js` 引用,无需改代码。 + +### 项目自有资源 + +以下文件由本项目创作,同样按 MIT 发布,**不属第三方**: + +- `workbuddy_portal/web/static/favicon.svg` +- `workbuddy_portal/web/static/css/app.css` +- `workbuddy_portal/web/static/js/app.js` +- `workbuddy_portal/web/static/dashboard/index.html` +- `docs/images/*.png`(界面截图,**使用合成示例数据**渲染,见下节) + +## 三、开发期工具(非运行依赖) + +| 组件 | 许可证 | 用途 | +|---|---|---| +| [Playwright for Python](https://playwright.dev/python/) | Apache-2.0 | `tools/shots.py` 登录后逐页截图 | +| [Pillow](https://python-pillow.org/) | MIT-CMU | 人工压缩文档配图时使用,未入库 | + +这些工具**不会**被打进 Docker 运行镜像的依赖里,也不影响部署方的义务。 + +## 四、文档与截图中的数据 + +`docs/images/` 下的界面截图与 `docs/` 中的 JSON 示例**全部使用合成数据**, +由 `tools/demo_data.py` 生成:模型名统一为 `demo-*`,客户端为 `vscode`/`webconsole`/`sdk`, +Prompt 为通用示例文本,审计 IP 取自 RFC 5737 的文档专用网段(`192.0.2.0/24`)。 +**不含任何真实账号、真实用量或第三方受版权保护的内容。** + +--- + +## 五、合规自查清单 + +二次分发或商用前,建议逐项确认: + +- [ ] `LICENSE` 与本文档随发行物一并提供 +- [ ] `vendor/echarts.min.js` 的头部许可证声明未被剥离或压缩掉 +- [ ] 若替换了 ECharts,同步更新本文档中的版本号 +- [ ] 若新增了第三方文件到仓库,在此登记其许可证 +- [ ] 若将本项目的界面截图用于宣传,确认其中不含真实业务数据 diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 61b83f1..6e708f5 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -1,4 +1,7 @@ #!/bin/sh +# SPDX-License-Identifier: MIT +# Copyright (c) 2026 Wang Chuanli + # ============================================================================= # workbuddy-portal 容器入口 # 1) 幂等初始化数据库(建表 + 默认配置 + 首个管理员) diff --git a/docker/healthcheck.py b/docker/healthcheck.py index fcbe5c1..16a1877 100644 --- a/docker/healthcheck.py +++ b/docker/healthcheck.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT +# Copyright (c) 2026 Wang Chuanli + """容器健康检查。 只用标准库:slim 镜像里没有 curl。`/login` 是唯一免登录页面,拿到 200 即认为 diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 381cf59..46f5e48 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -22,16 +22,31 @@ - **容器环境变量**:`WB_HOST` `WB_PORT` `WB_DATA_DIR` `WB_LOG_DIR` `WB_DB` `WB_ADMIN_USER` `WB_ADMIN_PASSWORD` `WB_DISABLE_SCHEDULER` `WB_IMPORT_CREDS` `WB_IMPORT_XLSX` - **文档体系** `docs/`: - [用户使用手册](USER-GUIDE.md)(含 9 张界面截图)、 + [用户使用手册](USER-GUIDE.md)(含 9 张界面截图,**全部用合成示例数据渲染**)、 [部署与运维指南](DEPLOYMENT.md)(含推镜像到 Gitea 注册表的完整流程)、 [架构与设计说明](ARCHITECTURE.md)、 [接口参考](API.md)、 [常见问题](FAQ.md) +- **开源声明体系**(仓库根目录):[LICENSE](../LICENSE)(MIT)、 + [THIRD-PARTY-NOTICES.md](../THIRD-PARTY-NOTICES.md)(依赖清单与再分发合规自查)、 + [CONTRIBUTING.md](../CONTRIBUTING.md)(含「必须遵守的不变量」与五层自检方法)、 + [SECURITY.md](../SECURITY.md)、[CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md)、 + `.github/` 下的 Issue 表单与 PR 模板、`.editorconfig`, + 以及给全部 Python / Shell 源文件加 `SPDX-License-Identifier: MIT` 头 +- **`tools/demo_data.py`**:生成**完全合成**的示例库(假模型名 / 假 Prompt / 偏斜的积分分布), + 写入 `data/demo/`(已在 `.gitignore` 内)。文档截图与本地调试都基于它, + 任何人不需要真实账号就能复现整套界面 - **`.gitattributes`**:强制 `*.sh` / `Dockerfile` / 各类源码为 LF (带 CRLF 的 `.sh` 在容器里会报 `exec format error`,极难定位) ### 变更 +- **文档数据脱敏**:9 张界面截图全部改用合成示例数据重拍; + `docs/API.md`、`docs/DEPLOYMENT.md` 示例响应里的真实模型名与真实统计数字一并替换为示例口径。 + 此前截图中含**真实 Prompt 全文**、本机路径与本机用户名,属于不该公开的内容 +- **`.gitignore` 补强**:只写 `data/*.sqlite` 会漏掉子目录,改为同时保留 `data/**/*.sqlite` + 等规则,并新增 `data/demo/` 忽略——否则 `tools/demo_data.py` 的产物会被误提交 + - **项目定名**:`wb_usage_portal` → **`workbuddy-portal`**; Python 包 `wb_usage` → **`workbuddy_portal`**;会话 cookie `wb_usage_sid` → `workbuddy_portal_sid`(升级后需要重新登录) diff --git a/manage.py b/manage.py index f6fcc32..3f2dc0b 100644 --- a/manage.py +++ b/manage.py @@ -1,5 +1,8 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT +# Copyright (c) 2026 Wang Chuanli + """WorkBuddy Portal —— 统一命令行入口。 采集 / 存储 / 呈现三件事都由本项目承担,不再依赖外部计划任务或自动化。 diff --git a/tools/check_live.py b/tools/check_live.py index df0befe..39ca878 100644 --- a/tools/check_live.py +++ b/tools/check_live.py @@ -1,5 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT +# Copyright (c) 2026 Wang Chuanli + """端到端验收:对**运行中的**服务发真实 HTTP 请求,走完整登录/CSRF/API 链路。 与 tests 里用 Flask test_client 的冒烟测试互补——这里验证的是「真的起起来了、 @@ -7,7 +10,7 @@ 用法: python tools/check_live.py # 默认 http://127.0.0.1:8848 - python tools/check_live.py --base http://10.0.0.5:8848 + python tools/check_live.py --base http://192.168.1.50:8848 # 换成你的部署主机 python tools/check_live.py -u admin -p 你的密码 python tools/check_live.py --from 2026-09-08 --to 2026-09-14 diff --git a/tools/push-all.sh b/tools/push-all.sh index 74b294b..865c7d2 100755 --- a/tools/push-all.sh +++ b/tools/push-all.sh @@ -1,4 +1,7 @@ #!/bin/sh +# SPDX-License-Identifier: MIT +# Copyright (c) 2026 Wang Chuanli + # ============================================================================= # 用 Gitea Access Token 一次性完成:推代码 + 推镜像 # diff --git a/tools/shots.py b/tools/shots.py index b667ef2..7768b38 100644 --- a/tools/shots.py +++ b/tools/shots.py @@ -1,5 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT +# Copyright (c) 2026 Wang Chuanli + """界面实检:登录后逐页截图,用来目视确认「统一美化」是否真的落地。 用法: diff --git a/tools/smoke.py b/tools/smoke.py index 44010cb..a5d9277 100644 --- a/tools/smoke.py +++ b/tools/smoke.py @@ -1,5 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT +# Copyright (c) 2026 Wang Chuanli + """离线回归:用 Flask test_client 对**真实库**做全页面只读渲染 + 缺陷防回归断言。 与 tools/check_live.py 的分工: diff --git a/workbuddy_portal/__init__.py b/workbuddy_portal/__init__.py index ac24306..f376761 100644 --- a/workbuddy_portal/__init__.py +++ b/workbuddy_portal/__init__.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT +# Copyright (c) 2026 Wang Chuanli + """WorkBuddy Portal —— 独立 Flask 项目。 一个程序管全部: diff --git a/workbuddy_portal/client.py b/workbuddy_portal/client.py index 5b6119f..1590cc0 100644 --- a/workbuddy_portal/client.py +++ b/workbuddy_portal/client.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT +# Copyright (c) 2026 Wang Chuanli + """云端用量接口客户端(纯 urllib,不依赖 requests/浏览器)。 接口:POST {api_base}/billing/meter/get-user-request-usage diff --git a/workbuddy_portal/collect.py b/workbuddy_portal/collect.py index 907def6..6dd7090 100644 --- a/workbuddy_portal/collect.py +++ b/workbuddy_portal/collect.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT +# Copyright (c) 2026 Wang Chuanli + """采集主流程:云端增量 -> SQLite(去重、断点、漂移校验、运行记录)。 与原 fetch_usage.py 的差别: diff --git a/workbuddy_portal/config.py b/workbuddy_portal/config.py index acaef8a..9ec388a 100644 --- a/workbuddy_portal/config.py +++ b/workbuddy_portal/config.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT +# Copyright (c) 2026 Wang Chuanli + """基础配置。 刻意保持「薄」:凡是运行期要改的东西(cookie、调度周期、采集参数)都放数据库 diff --git a/workbuddy_portal/db.py b/workbuddy_portal/db.py index 9fe4208..9837095 100644 --- a/workbuddy_portal/db.py +++ b/workbuddy_portal/db.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT +# Copyright (c) 2026 Wang Chuanli + """SQLite 访问层。 并发约定(重要): diff --git a/workbuddy_portal/query.py b/workbuddy_portal/query.py index a7ce776..4ea7422 100644 --- a/workbuddy_portal/query.py +++ b/workbuddy_portal/query.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT +# Copyright (c) 2026 Wang Chuanli + """SQL 聚合层:所有统计都在 SQLite 里算完再出去,页面不再搬运全量明细。 返回结构刻意与旧版 dashboard/data/*.json 的字段保持一致(d/c/k/fc/bc/m/h、 diff --git a/workbuddy_portal/scheduler.py b/workbuddy_portal/scheduler.py index 68b3038..67bf9ec 100644 --- a/workbuddy_portal/scheduler.py +++ b/workbuddy_portal/scheduler.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT +# Copyright (c) 2026 Wang Chuanli + """进程内调度器(手写,不依赖 APScheduler)。 为什么不引 APScheduler: diff --git a/workbuddy_portal/security.py b/workbuddy_portal/security.py index 0e217bb..8cd3efc 100644 --- a/workbuddy_portal/security.py +++ b/workbuddy_portal/security.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT +# Copyright (c) 2026 Wang Chuanli + """密码哈希、登录装饰器、CSRF。 局域网可访问 ⇒ 必须有鉴权。这里用 Werkzeug 自带的 PBKDF2,不引第三方依赖。 diff --git a/workbuddy_portal/web/__init__.py b/workbuddy_portal/web/__init__.py index 9e81c9e..de35d6d 100644 --- a/workbuddy_portal/web/__init__.py +++ b/workbuddy_portal/web/__init__.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT +# Copyright (c) 2026 Wang Chuanli + """Web 层包:蓝图注册。""" from . import api, views diff --git a/workbuddy_portal/web/api.py b/workbuddy_portal/web/api.py index 1077405..02cf4a5 100644 --- a/workbuddy_portal/web/api.py +++ b/workbuddy_portal/web/api.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT +# Copyright (c) 2026 Wang Chuanli + """JSON API —— ECharts 大屏与后台页面的数据入口。 约定: diff --git a/workbuddy_portal/web/views.py b/workbuddy_portal/web/views.py index f490e65..af430ed 100644 --- a/workbuddy_portal/web/views.py +++ b/workbuddy_portal/web/views.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT +# Copyright (c) 2026 Wang Chuanli + """页面路由(Jinja 模板)。 分工: