Files
cube/README.md
T

159 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# cube
Fam 的小 app 平台。
## 是什么
`cube` 是一个跑在 **`famzheng.me` 节点**hostname `famzheng.com`,单节点 k3s + traefik + gitea,公网 IP `178.104.186.206`)上的小 app 平台,专门收纳 Fam 自己写的一堆小工具/玩具 web app。底层硬件、k3s、Gitea、act_runner 等基础设施详见 `~/.claude/memory/infra.md`
主要任务:把目前散落在 `oci.euphon.net`Oracle Cloud ARM VM)上**值得留下**的 Fam 个人小 app 迁过来。oci 主机本身不退役,留给 Hera 同学继续用。
设计目标:反 Karpathy "web app 像拼宜家家具" 的困境 —— 全部自有基础设施,**零跨 dashboard 配置**,新 app 上线压缩到 5 分钟内。
---
## 平台约定
> 这是 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 一起 rebuildmonorepo 的整个意义就在于"一次重构,全员同步"。
### 部署目标
单一目标:famzheng.me 节点的 k3s`kubectl context default`),不双轨。
- 每 app 一个 k8s namespacens 名 = `cube-<app>`(如 `cube-cube` / `cube-portfolio`),便于跟其他 ns 隔离 + 一眼可见归属
- traefik ingress + 通配符 LE 证书自动签
### 域名
- `<app>.famzheng.me`wildcard A 记录已配,**零 DNS 操作**
- 不嵌 `cube` 子域(冲突检查 = 起新 ingress 时 traefik 自然报错,不需要额外心智)
- 旧域名(如 `portfolio.oci.euphon.net`)让 oci ingress 兜底 308 redirect 到新地址,过渡期后下掉
### 后端:Rust + Axum
每个 app 是 workspace 里的一个 bin crate,单 axum 服务。
`cube-core` 提供:
- `/healthz` router200 = 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
- 默认栈:**Vite + Vue 3 + TypeScript + Pinia + Vue Router**
- 选 Vue 而不是 Svelte 的原因:AIClaude / GPT)写 Vue 3 `<script setup>` + Composition API 训练语料密度大、bug 少;Svelte 5 runes 出来后 AI 经常混 4/5 语法
- build 输出到 `frontend/dist/`axum 用 `ServeDir` 同进程同容器同域名 serve
- **不搞前后端分离部署**、不搞独立前端域名、不搞独立 CI
### 构建:host musl + scratch 容器
host 上直接 cargo build,不在容器里跑 cargo。每个 app 的 build 命令固定:
```bash
cargo build --release --target x86_64-unknown-linux-musl -p <app>
(cd apps/<app>/frontend && npm ci && npm run build)
```
Dockerfile(每个 app 自带一份,结构一致):
```dockerfile
FROM scratch
COPY target/x86_64-unknown-linux-musl/release/<app> /app
COPY apps/<app>/frontend/dist /dist
ENTRYPOINT ["/app"]
```
- 镜像 < 20MB
- runner 上不跑 docker buildx 套娃,build 速度爆表
- prereq(首次设置 host):`apt install musl-tools` + `rustup target add x86_64-unknown-linux-musl`
### Container Registry
-**gitea 自带** Container RegistryPackages 功能,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/mochi/<app>:<sha>`
- ingress 已落地:`apps/cube/k8s/_registry-ingress.yaml`(用 `mochi` bot 账户的 token push
### CI/CD
走 gitea Actions,复用现有 instance-level act_runnerfam 用户,host shell 模式,labels `*:host`**新 repo 不用注册 runner**)。
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 -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"`fam 的 rustup 装在 `~/.cargo`)。
> ⚠️ workflow `runs-on:` 只能写 label 名(`ubuntu-latest`),**不要带 `:host` 后缀** —— act_runner 注册到 gitea 时只 declare label name,写后缀会一直 queued。
**不做 PR 预览环境** —— 个人小 app 不需要,徒增复杂度。
### 通用约定(每个 app 都要遵守)
- 暴露 `/healthz`200 = ok),k8s liveness/readiness 共用同一个 endpoint
- 日志统一 stdout`cube-core` 的 tracing 配 JSON),让 k3s 收
- 配置走 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: ... })` 是默认搭配。
---
## 当前状态
- **app #0 `cube` 已上线**2026-05-04):cube-core scaffold 完成,入口门户跑在 `cube.famzheng.me`CI 全链路(cargo musl → npm build → docker push → kubectl rollout)通了。
- **app #1 `simpleasm` 已上线**2026-05-04):从 oci 迁过来,跑在 `asm.famzheng.me`。FastAPI 重写为 axum + rusqlite,前端 Vue3 原样搬。oci 端旧域名 308 永久 redirect。
- 迁移源端清单 + 已迁完成的见 [`doc/todo.md`](doc/todo.md)。
## 迁移名单(截至 2026-05-04
- `portfolio`host systemd `portfolio.service`uvicorn `:8890`,**不在 k3s 里**;迁过来要重写成 Rust + axum 还是直接装 python 运行时?待定)
- `repo-vis`
- `simpleasm`
- `guitar`
- `pyroblem`(详情待补)
## 相关
- 宿主节点:`famzheng.me` / hostname `famzheng.com` —— 详见 `~/.claude/memory/infra.md`
- 迁移源:`oci.euphon.net`ARM Ubuntu 22.04 + k3s