app #0: cube.famzheng.me 入口门户 + 平台脚手架
deploy cube / build-and-deploy (push) Has been cancelled
deploy cube / build-and-deploy (push) Has been cancelled
monorepo 第一刀: - workspace + crates/cube-core(base router / healthz / ServeDir SPA fallback / JSON tracing / SIGTERM shutdown) - apps/cube:axum 主程序 + Vite + Vue 3 + TS 门户(暗色调 + 渐变 logo + app 卡片网格) - Dockerfile:scratch + musl 静态二进制,镜像 2.6MB - k8s/:cube-cube ns + Deployment + Service + Ingress(cube.famzheng.me,traefik LE 自动签) - registry:新增 registry.famzheng.me ingress 反代到 gitea 内置 container registry, 自动化身份用 mochi(registry.famzheng.me/mochi/cube) - CI:.gitea/workflows/deploy-cube.yml,host shell runner(gnoc), build → push → kubectl rollout 五步流水 - README:把宪法段改成 monorepo 模式 + monorepo 目录结构 - 新增宪法条款:前端视图状态走 URL(path + query)保证可 bookmark
This commit is contained in:
@@ -16,10 +16,35 @@ Fam 的小 app 平台。
|
||||
|
||||
> 这是 cube 上所有 app 的"宪法",所有 app 必须遵守,破例必须改这份文档并经 Fam 拍板。
|
||||
|
||||
### 仓库结构:monorepo + cargo workspace
|
||||
|
||||
整个 cube 是**一个 git 仓库** `fam/cube`,cargo workspace 模式。
|
||||
|
||||
```
|
||||
cube/
|
||||
├── Cargo.toml # workspace manifest
|
||||
├── crates/
|
||||
│ └── cube-core/ # 共享样板:base router / healthz / tracing / shutdown / config
|
||||
├── apps/
|
||||
│ ├── cube/ # app #0 = cube.famzheng.me 入口门户
|
||||
│ │ ├── Cargo.toml # path 依赖 cube-core
|
||||
│ │ ├── src/main.rs
|
||||
│ │ ├── frontend/ # vite + vue 3 + ts
|
||||
│ │ ├── Dockerfile
|
||||
│ │ └── k8s/ # deployment / service / ingress
|
||||
│ ├── portfolio/
|
||||
│ ├── repo-vis/
|
||||
│ └── ...
|
||||
├── doc/
|
||||
└── .gitea/workflows/ # CI;按 path 触发,per-app build/deploy
|
||||
```
|
||||
|
||||
`cube-core` 是 **path 依赖**(`cube-core = { path = "../../crates/cube-core" }`),不发布、不打 tag、不走 git 依赖。改 `cube-core` 时所有 app 一起 rebuild,monorepo 的整个意义就在于"一次重构,全员同步"。
|
||||
|
||||
### 部署目标
|
||||
|
||||
单一目标:famzheng.me 节点的 k3s(`kubectl context default`),不双轨。
|
||||
- 每 app 一个 k8s namespace(ns 名 = app 名,不加 cube- 前缀)
|
||||
- 每 app 一个 k8s namespace,ns 名 = `cube-<app>`(如 `cube-cube` / `cube-portfolio`),便于跟其他 ns 隔离 + 一眼可见归属
|
||||
- traefik ingress + 通配符 LE 证书自动签
|
||||
|
||||
### 域名
|
||||
@@ -28,23 +53,24 @@ Fam 的小 app 平台。
|
||||
- 不嵌 `cube` 子域(冲突检查 = 起新 ingress 时 traefik 自然报错,不需要额外心智)
|
||||
- 旧域名(如 `portfolio.oci.euphon.net`)让 oci ingress 兜底 308 redirect 到新地址,过渡期后下掉
|
||||
|
||||
### 后端:Rust + Axum,每 app 独立仓库
|
||||
### 后端:Rust + Axum
|
||||
|
||||
- gitea repo `fam/<app>`,单 axum 服务
|
||||
- 公共代码通过 `cube-core` crate 复用:`cube-core = { git = "https://famzheng.me/gitea/fam/cube-core", tag = "v0.x" }`
|
||||
- `cube-core` 提供:
|
||||
- `/healthz` router(200 = ok)
|
||||
- `ServeDir` 静态前端 fallback 到 `index.html`
|
||||
- `tracing` 配 JSON stdout
|
||||
- SIGTERM graceful shutdown
|
||||
- env → struct 配置加载(`envy` crate)
|
||||
- 业务 app 只写 `/api/*` 路由 + handler:
|
||||
```rust
|
||||
Router::new()
|
||||
.merge(cube_core::base("dist"))
|
||||
.nest("/api", api_routes())
|
||||
```
|
||||
- 不上 cargo workspace —— 每 app 一个 repo 彻底分
|
||||
每个 app 是 workspace 里的一个 bin crate,单 axum 服务。
|
||||
|
||||
`cube-core` 提供:
|
||||
- `/healthz` router(200 = ok)
|
||||
- `ServeDir` 静态前端 fallback 到 `index.html`(SPA 路由兼容)
|
||||
- `tracing` 配 JSON stdout
|
||||
- SIGTERM graceful shutdown
|
||||
- env → struct 配置加载
|
||||
|
||||
业务 app 只写 `/api/*` 路由 + handler:
|
||||
|
||||
```rust
|
||||
let app = cube_core::base("dist")
|
||||
.nest("/api", api_routes());
|
||||
cube_core::serve(app).await
|
||||
```
|
||||
|
||||
### 前端:Vite + Vue 3
|
||||
|
||||
@@ -55,19 +81,19 @@ Fam 的小 app 平台。
|
||||
|
||||
### 构建:host musl + scratch 容器
|
||||
|
||||
host 上直接 cargo build,不在容器里跑 cargo:
|
||||
host 上直接 cargo build,不在容器里跑 cargo。每个 app 的 build 命令固定:
|
||||
|
||||
```bash
|
||||
cargo build --release --target x86_64-unknown-linux-musl
|
||||
(cd frontend && npm ci && npm run build)
|
||||
cargo build --release --target x86_64-unknown-linux-musl -p <app>
|
||||
(cd apps/<app>/frontend && npm ci && npm run build)
|
||||
```
|
||||
|
||||
Dockerfile:
|
||||
Dockerfile(每个 app 自带一份,结构一致):
|
||||
|
||||
```dockerfile
|
||||
FROM scratch
|
||||
COPY target/x86_64-unknown-linux-musl/release/<app> /app
|
||||
COPY frontend/dist /dist
|
||||
COPY apps/<app>/frontend/dist /dist
|
||||
ENTRYPOINT ["/app"]
|
||||
```
|
||||
|
||||
@@ -80,20 +106,20 @@ ENTRYPOINT ["/app"]
|
||||
- 用 **gitea 自带** Container Registry(Packages 功能,gitea 1.20+ 自带)
|
||||
- **坑**:gitea 挂在 `/gitea/` 子路径下,docker daemon 默认拼 `https://famzheng.me/v2/...` 会 404
|
||||
- 方案:加一条 ingress `registry.famzheng.me` 反代 `/v2/*` → `gitea-svc:3000/v2/*`,复用 gitea token 鉴权
|
||||
- 镜像命名:`registry.famzheng.me/fam/<app>:<sha>`
|
||||
- 镜像命名:`registry.famzheng.me/mochi/<app>:<sha>`
|
||||
- **未实施**:第一个 app 上线时再搞 ingress;过渡期可本地 build 直接 `docker save` + `k3s ctr image import`
|
||||
|
||||
### CI/CD
|
||||
|
||||
走 gitea Actions,复用现有 instance-level act_runner(gnoc 用户,host shell 模式,labels `*:host`,**新 repo 不用注册 runner**)。
|
||||
|
||||
每个 app repo 一份 `.gitea/workflows/deploy.yml`,固定 5 步:
|
||||
monorepo 每 app 一份 workflow `.gitea/workflows/deploy-<app>.yml`,按 `paths` 触发(只改 `apps/<app>/**` 和 `crates/cube-core/**` 才跑)。固定 5 步:
|
||||
|
||||
1. `cargo build --release --target x86_64-unknown-linux-musl`
|
||||
2. `(cd frontend && npm ci && npm run build)`
|
||||
3. `docker build -t registry.famzheng.me/fam/<app>:$GITHUB_SHA .`
|
||||
4. `docker push registry.famzheng.me/fam/<app>:$GITHUB_SHA`
|
||||
5. `kubectl -n <app> set image deploy/<app> <app>=registry.famzheng.me/fam/<app>:$GITHUB_SHA`
|
||||
1. `cargo build --release --target x86_64-unknown-linux-musl -p <app>`
|
||||
2. `(cd apps/<app>/frontend && npm ci && npm run build)`
|
||||
3. `docker build -f apps/<app>/Dockerfile -t registry.famzheng.me/mochi/<app>:$GITHUB_SHA .`
|
||||
4. `docker push registry.famzheng.me/mochi/<app>:$GITHUB_SHA`
|
||||
5. `kubectl -n cube-<app> set image deploy/<app> <app>=registry.famzheng.me/mochi/<app>:$GITHUB_SHA`
|
||||
|
||||
host shell PATH 注意:workflow 第一行 `export PATH="$HOME/.cargo/bin:$PATH"`(gnoc 的 rustup 装在 `~/.cargo`)。
|
||||
|
||||
@@ -106,6 +132,7 @@ host shell PATH 注意:workflow 第一行 `export PATH="$HOME/.cargo/bin:$PATH
|
||||
- 配置走 env var + k8s Secret,**禁止** config.yaml 文件挂载(partiverse 那个"空目录顶替 config 文件"的坑别再踩)
|
||||
- 数据持久化只走 PVC + 每天 minio backup CronJob(`cube-core` 提供 base 模板)
|
||||
- 镜像 tag 用 git SHA,不用 `latest`
|
||||
- **前端视图状态走 URL**(path + query params),保证任意视图(筛选、分页、tab、详情)都能被 bookmark / share。可收藏的 state 必须在 URL 里,不准只活在 Pinia / `ref()` 里。Vue Router 的 `useRoute()` + `router.replace({ query: ... })` 是默认搭配。
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user