Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
19 changes: 17 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,24 @@ jobs:
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: pnpm install --frozen-lockfile
# 先构建 workspace 包:前端按 package.json exports 解析 @floatctf/*,
# 类型检查与构建都依赖 packages/*/dist 存在。
- run: pnpm run build:packages
# 架构边界 + 契约版本一致性(SDK 必须框架无关;bootstrap 不得带上默认前端……)
- run: ./scripts/check-architecture.sh
- run: pnpm --filter @floatctf/frontend-runtime lint
- run: pnpm --filter @floatctf/sdk lint
- run: pnpm --filter @floatctf/react lint
- run: pnpm --filter @floatctf/web lint
- run: pnpm --filter @floatctf/web test
- run: pnpm --filter @floatctf/web build
- run: pnpm --filter @floatctf/frontend-default lint
- run: pnpm run test:web
- run: pnpm run build:web
# 发布形态验证:打包 web-dist 并断言布局/manifest/脚本语法(与 release 同一脚本)。
- run: ./scripts/package-web-dist.sh /tmp/web-dist.tar.gz
- run: ./scripts/verify-release-frontend.sh /tmp/web-dist.tar.gz
# 前端管理器契约 / 安全负向测试(manifest 校验、注册表数据边界、制品树安全、
# 版本不可变、显式指针、构建身份非 root)。不依赖 docker。
- run: ./scripts/test-frontend-manager.sh

docker-e2e:
name: CI-docker · AWD infra smoke (on-demand)
Expand Down
32 changes: 24 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ jobs:
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: pnpm install --frozen-lockfile
- run: pnpm --filter @floatctf/web build
# workspace 包必须先构建:bootstrap 与 Default Frontend 都按 package.json 的
# exports 解析 @floatctf/*(指向 packages/*/dist)——这正是发布形态。
- run: pnpm run build:packages
- run: pnpm run build:web

# ── 后端(全 workspace 二进制)──
- uses: dtolnay/rust-toolchain@stable
Expand All @@ -36,11 +39,12 @@ jobs:
# 不构建 tests / benches / examples,也不为未发布的 workspace bin 产生本地 artifact。
- run: cargo build --locked --release -p floatctf -p floatctf-helper --bins

# ── 发布 4 个产物(供 install.sh 下载部署)──
# 1. floatctf API 二进制(installer 以此本地构建生产容器 image)
# ── 发布 5 个产物(供 install.sh 下载部署)──
# 1. floatctf API 二进制(installer 以此本地构建生产容器 image)
# 2. floatctf-helper 宿主控制面二进制
# 3. web-dist.tar.gz 前端静态产物
# 4. merged.sql 单个 SQL(fresh-DB bootstrap)
# 3. web-dist.tar.gz bootstrap 静态页 + 版本化 Default Frontend 制品
# 4. merged.sql 单个 SQL(fresh-DB bootstrap)
# 5. frontend.sh 前端管理器(安装到 $FLOATCTF_HOME/frontend.sh)
- name: Assemble release artifacts
shell: bash
run: |
Expand All @@ -52,15 +56,26 @@ jobs:
# 2) 宿主控制面
install -m 0755 target/release/floatctf-helper floatctf-helper

# 3) 前端静态产物
tar -czf web-dist.tar.gz -C apps/web/dist .
# 3) Web 产物(bootstrap/ + frontends/default/<version>/,布局由脚本保证)
bash scripts/package-web-dist.sh web-dist.tar.gz

# 4) merged.sql(确定性 fresh-DB bootstrap)
bash apps/api/src/sql/migrate.sh make
install -m 0644 apps/api/src/sql/merged.sql merged.sql

# 5) 前端管理器(生产无需源码签出即可安装前端)
install -m 0755 scripts/frontend.sh frontend.sh

echo "release artifacts:"
ls -la floatctf floatctf-helper web-dist.tar.gz merged.sql
ls -la floatctf floatctf-helper web-dist.tar.gz merged.sql frontend.sh

- name: Verify frontend release artifacts
shell: bash
run: |
set -euo pipefail
# 断言归档布局、Default Frontend manifest/entry/styles、无源码残留、
# 脚本语法,并用 frontend-runtime 的**真实校验器**再验一遍 manifest。
bash scripts/verify-release-frontend.sh web-dist.tar.gz

- name: Verify production API image
shell: bash
Expand Down Expand Up @@ -90,4 +105,5 @@ jobs:
floatctf-helper
web-dist.tar.gz
merged.sql
frontend.sh
generate_release_notes: true
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
target/
node_modules/
# pnpm 的本地 content-addressable store(某些 CI/离线安装配置会在仓库根创建)
/.pnpm-store/
dist/
.env
.env.*
Expand Down
14 changes: 11 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## 仓库一句话

基于 Rust(Actix Web + SeaORM + PostgreSQL + RustFS)与 React 的 CTF 实训/竞赛平台(Jeopardy + AWD 双赛制),monorepo 结构:`apps/api`(后端)、`apps/web`(前端)、`crates/`(共享与独立服务)。
基于 Rust(Actix Web + SeaORM + PostgreSQL + RustFS)与 React 的 CTF 实训/竞赛平台(Jeopardy + AWD 双赛制),monorepo 结构:`apps/api`(后端)、`apps/web`(**Web bootstrap 引导页**)、`frontends/default`(**官方前端 = 当前完整 UI**)、`packages/{sdk,react,frontend-runtime}`(可插拔前端平台)、`crates/`(共享与独立服务)。

## HANDOFF.md(会话交接记忆)

Expand All @@ -21,18 +21,21 @@
| [docs/agents/DATA-FETCHING.md](docs/agents/DATA-FETCHING.md) | **前端数据页面**:缓存分级、keepPreviousData、queryKey 失效 |
| [docs/agents/TESTING.md](docs/agents/TESTING.md) | 测试规范:层级、写法、禁忌 |
| [docs/agents/RULES.md](docs/agents/RULES.md) | **用户反复强调的规则与返工教训**:前端形态细则、禁原生弹窗、数据必须真实、环境约定 |
| [docs/frontend/ARCHITECTURE.md](docs/frontend/ARCHITECTURE.md) | **可插拔前端平台**:Frontend ≠ Theme、依赖方向、mount 契约、注册表/版本、信任模型与安全审查 |
| [docs/frontend/DEVELOPING.md](docs/frontend/DEVELOPING.md) | 前端开发:官方前端 / 外部仓库 / 安装激活回滚 |
| [docs/frontend/ARTIFACT.md](docs/frontend/ARTIFACT.md) | 制品:manifest 字段、路径安全规则、注册表 schema、归档格式、构建隔离 |

## 铁律(违反即返工)

1. **配置只从 TOML 读取**:`FLOATCTF_CONFIG` → `apps/api/config/development.toml` → `AppConfig`(经 `ReqCtx.config` 注入)。禁止新增环境变量读取;动态设置用 `get_setting`。
2. **Migrations 只前进,无论如何都不可以直接动已有迁移文件**:`apps/api/src/sql/migrations/` 下**已经存在**的文件(含 baseline `20260810121925-initial-schema.sql` / `20260810121926-initial-data.sql`,以及任何已提交或已 apply 的迁移)**绝对禁止**直接修改、删除、重命名、重写、squash、回滚式改写。改 Schema / 修 Schema 错误 / 补数据 **只能** `mise run db:migration:new <名称>` 追加**新**迁移,把 SQL 写进这个新文件。禁止手改 `merged.sql`(生成产物);禁止在 migration 或业务代码里操作 `schema_migrations`(由 migrate.sh 独占)。唯一允许写入的对象:刚 `new` 出来、尚未 apply、尚未作为历史固化的**新文件**。详细见 [DATABASE.md](docs/agents/DATABASE.md)。
3. **实体是生成的**:手改 `apps/api/src/entity/` / `apps/web/src/entity/` 会被覆盖。Schema 变更流程:`db:migration:new` → 写幂等 SQL + 中文 COMMENT(**文件内禁止 BEGIN/COMMIT**)→ `db:migration:validate` → `db:migration:apply` → `mise run db:gen`。`merged.sql` 由 release 流程从 migrations 确定性生成;日常开发启动不依赖它。**sea-orm-cli 必须 1.1.20**。`public.schema_migrations` 不是领域实体(generator 已排除)。API 计算字段(如 settings `resolved_value`)放 manual DTO,不要写回生成文件。
3. **实体是生成的**:手改 `apps/api/src/entity/` / `packages/sdk/src/entity/` 会被覆盖。Schema 变更流程:`db:migration:new` → 写幂等 SQL + 中文 COMMENT(**文件内禁止 BEGIN/COMMIT**)→ `db:migration:validate` → `db:migration:apply` → `mise run db:gen`。`merged.sql` 由 release 流程从 migrations 确定性生成;日常开发启动不依赖它。**sea-orm-cli 必须 1.1.20**。`public.schema_migrations` 不是领域实体(generator 已排除)。API 计算字段(如 settings `resolved_value`)放 manual DTO,不要写回生成文件。
4. **三处一致**:数据库 Schema / 生成实体 / 业务代码引用必须一致(`entity/代码/库` 漂移是历史最高频 bug 源)。
5. **敏感值走 `Secret`**:Debug/日志必须脱敏;`auth.jwt_secret` 等不落日志、不入库。
6. **宿主权限只走 helper**:生产 API 是 Compose 内的非 root 容器,必须保持 `cap_drop=ALL`、`no-new-privileges`、read-only rootfs,禁止挂载 `/var/run/docker.sock`、加入 docker 组或获得 `CAP_NET_ADMIN`;Docker 走 `helper-docker.sock`,WireGuard/nftables/conntrack 走 `helper-control.sock`。新增高权限能力必须先扩展 helper 的受限协议/策略。宿主 systemd 只管理 `floatctf-helper.service` + `floatctf-infra.service`/`floatctf.target`,不要恢复 `floatctf-api.service`。
7. **提交规范**:中文 message(feat/fix/chore/docs/refactor 前缀),按角度分批提交;提交前 `cargo fmt --all && cargo check -p floatctf` 与相关测试全绿。**push 前必须本地完整过一遍验证**:`mise run check` 全绿,前端额外 `tsc --noEmit` 与 `vite build` 通过(CI 跑的是 `vite build && tsc`,本地不绿推送必红)。
8. **先诊断后修复**:修 bug 先定位根因并给证据;涉及行为/数据变更,先向用户说明方案获批后再动手。
9. **前端仿照既有页面**:新页面先找同域参照页(赛事详情参照 `service/events/jeopardy.$id/*` 与 `awd.$id/*`、管理列表参照 `admin/challenges.tsx`、导航配置参照 `navigation/*`),结构/布局/交互与参照页保持一致;优先复用 `components/` 现有组件(GenericTable、EventStatusBadge、SubmitWriteup、MsgBanner、AppLink、FilterBar 等)与 `@primer/react`,禁止另起炉灶自创视觉风格或手写重复实现。管理页必须用 Challenges 内置 GenericTable 增删改查形态、样式对照要"一模一样"、默认最简方案等细则与全部返工案例见 [RULES.md](docs/agents/RULES.md)。
9. **前端仿照既有页面**:官方前端现在位于 `frontends/default/`;新页面先找同域参照页(赛事详情参照 `service/events/jeopardy.$id/*` 与 `awd.$id/*`、管理列表参照 `admin/challenges.tsx`、导航配置参照 `navigation/*`),结构/布局/交互与参照页保持一致;优先复用 `components/` 现有组件(GenericTable、EventStatusBadge、SubmitWriteup、MsgBanner、AppLink、FilterBar 等)与 `@primer/react`,禁止另起炉灶自创视觉风格或手写重复实现。管理页必须用 Challenges 内置 GenericTable 增删改查形态、样式对照要"一模一样"、默认最简方案等细则与全部返工案例见 [RULES.md](docs/agents/RULES.md)。
10. **用户偏好(详细见 [RULES.md](docs/agents/RULES.md))**:禁止原生 `alert(`/`confirm(` 弹窗(一律用 Primer `useConfirm`/`Dialog`/`useMsgBanner`);展示数据必须来自真实接口、禁止假数据/占位糊弄("不要随便搞点数据糊弄我"),状态判定必须与后端一致。

## 常用命令速查
Expand All @@ -49,12 +52,17 @@ mise run db:migration:apply # 执行未应用的迁移,fresh D
mise run db:migration:merge # release/fresh-production 用 merged.sql
mise run db:gen # 重新生成 Rust 实体 + TS 类型
mise run fmt / lint / test / check / build # 质量门禁
mise run web:typecheck # 五个前端包 tsc --noEmit
mise run web:architecture # 前端架构边界 + 契约版本一致性(失败即红)
mise run dev:packages # 改动 packages/* 时以 watch 重建 dist
scripts/frontend.sh list|info|verify|install|remove|set-current # 前端管理器(生产装在 $FLOATCTF_HOME/frontend.sh)
cargo test -p floatctf <关键词> # 跑指定单元测试
```

## 开发环境速记

- 唯一开发模式:`mise run dev`;不存在 A/B 双轨与 `floatctf-dev-infra.service`。
- 可插拔前端:`apps/web` 是引导页,官方前端在 `frontends/default`;`mise run dev` 先构建 `packages/*` 再起 Default Frontend 的 Vite(HMR)。前端存储 `$FLOATCTF_HOME/frontends/`,管理器 `$FLOATCTF_HOME/frontend.sh`,激活是设置 `FRONTEND_ACTIVE`,破窗是 `?frontend=default`。
- API:当前开发者 UID 运行,`watchexec` 自动重编译/重启;`dev-api-run.sh` 每次启动时丢弃 docker 等附加组和 capabilities;Web:Vite HMR。
- 宿主控制面:`floatctf-helper.service`,用户 `floatctf-helper` + docker group + `CAP_NET_ADMIN`;API 通过 `/run/floatctf/helper-control.sock` 与 `/run/floatctf/helper-docker.sock` 调用,API 自身无 Docker socket / 网络 capability。
- 开发库:`postgres://postgres:postgres@127.0.0.1:5432/floatctf_db`;fresh DB 直接 `migration apply`,开发启动不需要 `merged.sql`。
Expand Down
39 changes: 36 additions & 3 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,15 +218,36 @@ net.bridge.bridge-nf-call-ip6tables=1

## 6. Release 产物与 API image

`v*` tag 触发 `.github/workflows/release.yml`,发布四个部署产物:
`v*` tag 触发 `.github/workflows/release.yml`,发布五个部署产物:

```text
floatctf API release binary
floatctf-helper host control plane binary
web-dist.tar.gz Web static dist
web-dist.tar.gz bootstrap 引导页 + 版本化 Default Frontend 制品
merged.sql fresh PostgreSQL bootstrap
frontend.sh 前端管理器(安装到 $FLOATCTF_HOME/frontend.sh)
```

`web-dist.tar.gz` 的布局是固定契约(由 `scripts/package-web-dist.sh` 组装、
`scripts/verify-release-frontend.sh` 在发布前断言):

```text
bootstrap/ # 引导页(无 React / 无 UI)
index.html
assets/…
frontends/
default/
<version>/ # 版本化不可变制品
frontend.json
assets/{frontend.js,frontend.css,<chunks>.js}
```

安装器把 `bootstrap/` 铺到 `$FLOATCTF_HOME/web`,把 `frontends/` 交给前端管理器安装
(`$FLOATCTF_HOME/frontend.sh install … --platform --make-current`):
**升级只更新 release 里的前端,第三方已安装的前端、其版本与注册表指针一律保留**,
`FRONTEND_ACTIVE` 设置也不会被改动。详见
[docs/frontend/ARCHITECTURE.md](docs/frontend/ARCHITECTURE.md)。

仍然发布原始 API binary,是为了让安装器无需依赖外部容器 registry。部署阶段会使用:

```text
Expand Down Expand Up @@ -263,7 +284,8 @@ sudo env SITE_ADDRESS=ctf.example.com bash install.sh \
--api-url <floatctf-url> \
--helper-url <floatctf-helper-url> \
--web-url <web-dist.tar.gz-url> \
--migrate-url <merged.sql-url>
--migrate-url <merged.sql-url> \
--frontend-manager-url <frontend.sh-url>
```

等价环境变量:
Expand All @@ -273,6 +295,7 @@ FLOATCTF_API_URL
FLOATCTF_HELPER_URL
FLOATCTF_WEB_URL
FLOATCTF_MIGRATE_URL
FLOATCTF_FRONTEND_MANAGER_URL
FLOATCTF_VERSION
```

Expand Down Expand Up @@ -350,9 +373,19 @@ enable units(不启动整个平台)
├── compose.prod.yml
├── merged.sql
├── .env
├── web/ # bootstrap 引导页(Caddy root /srv/web)
├── frontends/ # 已安装前端(Caddy 只读挂载到 /srv/frontends)
│ ├── registry.json # 本地注册表(no-store)
│ └── default/<version>/ # 平台内置前端(受保护、版本化)
├── frontend.sh # 前端管理器(root:root 0755;无需源码签出)
└── uninstall.sh
```

前端生命周期:安装/升级/回滚用 `sudo $FLOATCTF_HOME/frontend.sh install|set-current|remove`;
**激活**在管理端 → 设置 → 前端(写动态设置 `FRONTEND_ACTIVE`);破窗恢复在任意页面加
`?frontend=default`。安全卸载(不带 `--purge`)**保留** `frontends/` 与 `frontend.sh`,
因此重新部署能恢复同一套前端;`--purge` 才会一并删除。

说明:`data/`、`logs/`、`runtime/` 属主是 API 容器的数值身份 `65532:floatctf`;
根目录本身是 `root:floatctf 0750`。Redis 的数据落在 Compose named volume
`floatctf-redis-data`(不是 `data/redis/`)。
Expand Down
Loading
Loading