Fam Zheng b2bec0406f
deploy write / build-and-deploy (push) Successful in 1m28s
write(ui): 全套 dark 主题,配色对齐 notes / cube portal
- 引入 :root --bg/--bg-elev/--bg-card/--border/--text/--accent 等 vars(跟 notes 一致)
- 主要 surface:body / sidebar / editor / preview / input-bar / modal / mobile-bar
- accent 紫 (#c084fc + #7c5cbf) 替代 #5566ee 蓝
- preview markdown:h2-h4 紫,inline code amber,pre block 加 border
- splitter hover 用 accent-strong 紫
- 滚动条暗化
2026-05-24 17:45:26 +01:00

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.netOracle Cloud ARM VM)上值得留下的 Fam 个人小 app 迁过来。oci 主机本身不退役,留给 Hera 同学继续用。

设计目标:反 Karpathy "web app 像拼宜家家具" 的困境 —— 全部自有基础设施,零跨 dashboard 配置,新 app 上线压缩到 5 分钟内。


平台约定

这是 cube 上所有 app 的"宪法",所有 app 必须遵守,破例必须改这份文档并经 Fam 拍板。

仓库结构:monorepo + cargo workspace

整个 cube 是一个 git 仓库 fam/cubecargo 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-corepath 依赖cube-core = { path = "../../crates/cube-core" }),不发布、不打 tag、不走 git 依赖。改 cube-core 时所有 app 一起 rebuildmonorepo 的整个意义就在于"一次重构,全员同步"。

部署目标

单一目标:famzheng.me 节点的 k3skubectl context default),不双轨。

  • 每 app 一个 k8s namespacens 名 = cube-<app>(如 cube-cube / cube-portfolio),便于跟其他 ns 隔离 + 一眼可见归属
  • traefik ingress + 通配符 LE 证书自动签

域名

  • <app>.famzheng.mewildcard 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.htmlSPA 路由兼容)
  • tracing 配 JSON stdout
  • SIGTERM graceful shutdown
  • env → struct 配置加载

业务 app 只写 /api/* 路由 + handler

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 命令固定:

cargo build --release --target x86_64-unknown-linux-musl -p <app>
(cd apps/<app>/frontend && npm ci && npm run build)

Dockerfile(每个 app 自带一份,结构一致):

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 都要遵守)

  • 暴露 /healthz200 = ok),k8s liveness/readiness 共用同一个 endpoint
  • 日志统一 stdoutcube-core 的 tracing 配 JSON),让 k3s 收
  • 配置走 env var + k8s Secret禁止 config.yaml 文件挂载(partiverse 那个"空目录顶替 config 文件"的坑别再踩)
  • 数据持久化只走 PVC + 每天 minio backup CronJobcube-core 提供 base 模板)
  • 镜像 tag 用 git SHA,不用 latest
  • 前端视图状态走 URLpath + 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.meCI 全链路(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

迁移名单(截至 2026-05-04

  • portfoliohost systemd portfolio.serviceuvicorn :8890不在 k3s 里;迁过来要重写成 Rust + axum 还是直接装 python 运行时?待定)
  • repo-vis
  • simpleasm
  • guitar
  • pyroblem(详情待补)

相关

  • 宿主节点:famzheng.me / hostname famzheng.com —— 详见 ~/.claude/memory/infra.md
  • 迁移源:oci.euphon.netARM Ubuntu 22.04 + k3s
S
Description
Fam 的小 app 平台 — famzheng.me k3s 上的部署/脚手架
Readme 26 MiB
Languages
Vue 38.8%
Rust 26.1%
Python 12.8%
TypeScript 8.1%
JavaScript 8%
Other 6.2%