171 lines
6.0 KiB
Markdown
171 lines
6.0 KiB
Markdown
# Stash
|
||
|
||
Stash 是一个用 Rust 编写的本地静态页面托管工具。编译后的 React 控制台会被内嵌进二进制,
|
||
所以发布物只有一个 `stash` 可执行文件,不需要额外部署前端静态资源。
|
||
|
||
它能做两件事:托管目录下的多个静态前端项目、给控制台提供同源 JSON / SSE 接口。
|
||
|
||
## 快速开始
|
||
|
||
```bash
|
||
# 1. 先构建前端(dist 会被内嵌,缺了它 cargo build 会直接失败)
|
||
cd stash-web
|
||
npm install
|
||
npm run build
|
||
cd ..
|
||
|
||
# 2. 编译并运行
|
||
cargo run # 托管 ./public,监听 127.0.0.1:8080
|
||
|
||
# 自定义目录 / 端口 / 对外暴露
|
||
cargo run -- --root ./sites --port 3000
|
||
cargo run -- --root ./sites --host 0.0.0.0 --port 3000
|
||
```
|
||
|
||
启动后打开终端里打印的地址即可,例如 `http://127.0.0.1:8080/`。
|
||
默认托管目录 `./public` 不存在时会自动创建。
|
||
|
||
环境要求:
|
||
|
||
- Rust 工具链(2024 Edition)
|
||
- Node.js 24,推荐用 [Volta](https://volta.sh/) 管理;`stash-web/package.json` 里已用 `volta` 字段固定版本
|
||
|
||
## 工作方式
|
||
|
||
请求按下面的顺序命中:
|
||
|
||
| 路径 | 处理方式 |
|
||
| --- | --- |
|
||
| `/api/*` | JSON / SSE 接口 |
|
||
| `/`、`/index.html` | 内嵌前端的 `index.html`(`no-cache`) |
|
||
| `/assets/**` | 内嵌前端的静态资源(文件名带 hash,长缓存) |
|
||
| `/favicon.svg` | 内嵌前端根目录文件 |
|
||
| 其他路径 | `ServeDir` 托管 `--root` 目录,目录自动补 `index.html` |
|
||
| 仍未命中 | 内置 404 页面(显示请求路径) |
|
||
|
||
托管目录下的每个一级子目录,只要包含 `index.html` 或 `index.htm`,就会被识别为一个项目,
|
||
通过 `/<目录名>/` 访问。目录名会做 URL 路径段编码。
|
||
|
||
## 目录结构
|
||
|
||
```text
|
||
.
|
||
├── Cargo.toml # 单 crate(lib + bin),依赖清单
|
||
├── Cargo.lock
|
||
├── build.rs # 把 stash-web/dist 同步到 $OUT_DIR/web
|
||
├── src/ # Rust 后端
|
||
│ ├── main.rs # CLI 参数、启动横幅、停机信号
|
||
│ ├── lib.rs # 库导出
|
||
│ ├── config.rs # 服务配置
|
||
│ ├── error.rs # 错误类型与 JSON 错误响应
|
||
│ ├── server.rs # 路由、内嵌静态资源、SSE、生命周期
|
||
│ ├── listing.rs # 托管目录扫描
|
||
│ └── watcher.rs # 文件监听、防抖、快照广播
|
||
├── stash-web/ # React + Vite + TypeScript 控制台
|
||
│ ├── package.json # 含 volta 版本固定
|
||
│ ├── vite.config.ts # 开发期 /api 代理到 127.0.0.1:8080
|
||
│ └── src/
|
||
└── public/ # 默认托管目录(可放多个前端项目)
|
||
```
|
||
|
||
`templates/` 与 `assets/` 是上一版 Askama 控制台留下的文件,当前已无任何代码引用,
|
||
可以直接删除。
|
||
|
||
## 前端怎么被内嵌
|
||
|
||
1. `npm run build` 产出 `stash-web/dist`;
|
||
2. `build.rs` 在编译 Rust 之前把 `dist` 复制到 `$OUT_DIR/web`(先清空,避免旧 hash 文件残留);
|
||
3. `src/server.rs` 用 `include_dir!("$OUT_DIR/web")` 在编译期把整棵目录树嵌进二进制;
|
||
4. 二进制运行时不再读取任何前端文件。
|
||
|
||
构建顺序不能颠倒:`dist` 不存在时 `build.rs` 会带着提示信息直接失败,而不是产出一个没有界面的二进制。
|
||
|
||
`build.rs` 声明了 `rerun-if-changed`,改动 `dist` 后重新 `cargo build` 会自动重新嵌入。
|
||
|
||
## 前端开发
|
||
|
||
```bash
|
||
cd stash-web
|
||
npm install
|
||
npm run dev # http://127.0.0.1:5173,/api 自动代理到后端 8080
|
||
npm run build # 产物输出到 stash-web/dist
|
||
npm run lint # oxlint
|
||
```
|
||
|
||
开发时前后端分开跑:一个终端 `cargo run`,另一个终端 `npm run dev`。
|
||
控制台的用户资料目前是占位数据(见 `stash-web/src/profile.ts`),后端尚未提供鉴权接口。
|
||
|
||
## 命令行参数
|
||
|
||
| 参数 | 默认值 | 说明 |
|
||
| --- | --- | --- |
|
||
| `-r, --root <DIR>` | `./public` | 静态页面托管根目录 |
|
||
| `-p, --port <PORT>` | `8080` | 监听端口 |
|
||
| `--host <HOST>` | `127.0.0.1` | 监听地址,`0.0.0.0` 供局域网访问 |
|
||
| `--no-cors` | 关闭 | 关闭全局 CORS 标头注入 |
|
||
| `-h, --help` | - | 帮助 |
|
||
| `-V, --version` | - | 版本 |
|
||
|
||
`--no-listing` 参数目前仍能被解析,但由于控制台已改为内嵌前端,它不再影响任何行为。
|
||
|
||
## HTTP API
|
||
|
||
### 健康检查
|
||
|
||
```http
|
||
GET /api/health
|
||
```
|
||
|
||
```json
|
||
{ "status": "ok", "service": "stash" }
|
||
```
|
||
|
||
### 项目列表
|
||
|
||
```http
|
||
GET /api/projects
|
||
```
|
||
|
||
```json
|
||
{
|
||
"projects": [
|
||
{ "name": "admin-console", "route_path": "/admin-console/" },
|
||
{ "name": "docs", "route_path": "/docs/" }
|
||
]
|
||
}
|
||
```
|
||
|
||
### 订阅项目变化
|
||
|
||
```http
|
||
GET /api/projects/events
|
||
Accept: text/event-stream
|
||
```
|
||
|
||
连接建立后立即推送一次当前快照,之后在项目列表变化时推送:
|
||
|
||
```text
|
||
event: projects
|
||
data: {"projects":[{"name":"docs","route_path":"/docs/"}]}
|
||
```
|
||
|
||
服务端递归监听托管目录,把短时间内的连续事件合并成一次扫描,只在项目名称或访问路径真正变化时广播;
|
||
每 15 秒发送一次保活消息,浏览器断开后自动重连。
|
||
|
||
## 测试
|
||
|
||
```bash
|
||
cargo test # 目录扫描、隐藏目录过滤、排序、路径编码、文件监听广播
|
||
cd stash-web && npm run build # tsc -b + vite build,同时做类型检查
|
||
```
|
||
|
||
## 已知限制
|
||
|
||
- 控制台是单页应用但没有客户端路由,因此没有 SPA history 回退;刷新不存在的物理路径会返回 404。
|
||
- 内嵌资源占用了 `/assets/` 前缀,托管目录下如果存在同名目录会被遮蔽。
|
||
- `/` 固定由控制台占用,上一版「根目录有 `index.html` 就直接当单站点展示」的行为已取消;
|
||
托管目录里的项目统一走 `/<目录名>/`。
|
||
- 项目扫描只检查一级子目录,不递归查找更深层项目。
|
||
- 文件事件经过约 150ms 防抖,新项目出现在控制台可能有短暂延迟。
|
||
- 没有身份认证,默认只建议绑定 `127.0.0.1`;使用 `0.0.0.0` 暴露到局域网前请确认目录内容适合被其他设备访问。
|