891 lines
18 KiB
Markdown
891 lines
18 KiB
Markdown
# Havline 飞牛统一网关应用改造方案
|
||
|
||
> 日期:2026-10-02
|
||
> 适用范围:`fnos/havline` 飞牛应用包与 Havline 主程序 1.9.x
|
||
> 官方依据:飞牛应用开放平台「统一网关」「应用入口」「应用资源」「环境变量」「Docker 应用案例」
|
||
|
||
## 一、结论先行
|
||
|
||
统一网关不是 Docker 应用模板的替代品,而是飞牛应用的一种**访问模型**。
|
||
|
||
官方文档明确说明:Docker 应用也可以使用统一网关。做法是把
|
||
`${TRIM_APPDEST}` 挂载到容器内,让容器中的服务在安装目录下创建
|
||
Unix Socket,再由 fnOS 网关转发请求。
|
||
|
||
因此 Havline 的正确改造方向是:
|
||
|
||
```text
|
||
Docker 运行形态(保留)
|
||
+ 统一网关访问模型(新增)
|
||
+ 端口服务回退(保留)
|
||
```
|
||
|
||
而不是把现有 Docker 应用整体推翻重做。
|
||
|
||
推荐首版边界:
|
||
|
||
```text
|
||
管理界面:
|
||
通过 /app/havline 和 Unix Socket 暴露
|
||
|
||
公网反代、DDNS、证书、FRP、Cloudflare Tunnel:
|
||
仍由 Havline 自己管理
|
||
|
||
端口 18080/9443:
|
||
作为公网反代功能端口和回退入口保留
|
||
```
|
||
|
||
## 二、统一网关是什么
|
||
|
||
飞牛统一网关为应用提供飞牛 fnOS 访问域名下的稳定系统 URL。
|
||
|
||
请求路径:
|
||
|
||
```text
|
||
/app/{appname}
|
||
/app/{appname}/{customPath}
|
||
```
|
||
|
||
工作流程:
|
||
|
||
1. 用户在飞牛桌面打开应用。
|
||
2. fnOS 先校验用户会话。
|
||
3. fnOS 将请求转发到应用声明的 Unix Socket。
|
||
4. 应用读取网关注入的用户 Header。
|
||
5. 应用继续执行自己的业务权限校验。
|
||
|
||
统一网关与端口服务的对比:
|
||
|
||
| 能力 | 端口服务 | 统一网关 |
|
||
| --- | --- | --- |
|
||
| 访问地址 | 独立端口 | `/app/{appname}` |
|
||
| NAS 登录态 | 不接入 | 转发前校验 |
|
||
| 用户上下文 | 无 | `X-Trim-*` Header |
|
||
| WebSocket | 应用自行处理 | 支持 |
|
||
| 常驻服务 | 支持 | 支持 |
|
||
| 部署形态 | 任意 | 任意,Docker 也支持 |
|
||
| 核心适配 | 监听端口 | 监听 Unix Socket |
|
||
|
||
## 三、官方字段与约束
|
||
|
||
### 3.1 应用入口
|
||
|
||
统一网关入口写在 `app/ui/config`:
|
||
|
||
```json
|
||
{
|
||
".url": {
|
||
"havline.main": {
|
||
"title": "Havline",
|
||
"icon": "images/icon_{0}.png",
|
||
"type": "iframe",
|
||
"protocol": "",
|
||
"gatewayPrefix": "/app/havline",
|
||
"gatewaySocket": "app.sock",
|
||
"url": "/app/havline",
|
||
"allUsers": false,
|
||
"control": {
|
||
"accessPerm": "readonly"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
字段规则:
|
||
|
||
```text
|
||
gatewayPrefix
|
||
必须使用 /app/{appname} 或 /app/{appname}/{customPath}
|
||
发布后应保持稳定
|
||
|
||
gatewaySocket
|
||
只填写 Socket 文件名,例如 app.sock
|
||
实际路径为 ${TRIM_APPDEST}/app.sock
|
||
|
||
protocol / port
|
||
在统一网关入口中被忽略,不参与路由
|
||
```
|
||
|
||
### 3.2 Docker 应用接入
|
||
|
||
官方 Docker 案例给出的结构:
|
||
|
||
```yaml
|
||
services:
|
||
web:
|
||
image: my-app:latest
|
||
container_name: hello-docker-web
|
||
restart: unless-stopped
|
||
volumes:
|
||
- "${TRIM_APPDEST}:/app/target:rw"
|
||
environment:
|
||
GATEWAY_SOCKET: /app/target/app.sock
|
||
```
|
||
|
||
容器内服务监听:
|
||
|
||
```text
|
||
/app/target/app.sock
|
||
```
|
||
|
||
宿主机对应路径:
|
||
|
||
```text
|
||
/var/apps/{appname}/target/app.sock
|
||
```
|
||
|
||
### 3.3 用户 Header
|
||
|
||
网关校验会话后注入:
|
||
|
||
| Header | 含义 | 示例 |
|
||
| --- | --- | --- |
|
||
| `X-Trim-Userid` | 当前用户 UID | `1000` |
|
||
| `X-Trim-Isadmin` | 是否管理员 | `true` / `false` |
|
||
| `X-Trim-Username` | 当前用户名 | `admin` |
|
||
|
||
这些 Header 只能作为网关可信上下文使用。应用仍然需要校验:
|
||
|
||
```text
|
||
接口权限
|
||
资源归属
|
||
文件路径
|
||
高风险操作
|
||
```
|
||
|
||
不能信任客户端自己传入的用户 ID。
|
||
|
||
## 四、当前 Havline 的差距
|
||
|
||
### 4.1 当前监听方式
|
||
|
||
```text
|
||
HAVLINE_LISTEN=:6893
|
||
```
|
||
|
||
主程序使用 TCP 端口监听:
|
||
|
||
```text
|
||
internal/config/config.go
|
||
ListenAddr = HAVLINE_LISTEN,默认 :6893
|
||
|
||
internal/app/app.go
|
||
http.Server{Addr: cfg.ListenAddr}
|
||
server.ListenAndServe()
|
||
```
|
||
|
||
当前没有 Unix Socket 监听能力。
|
||
|
||
### 4.2 当前飞牛入口
|
||
|
||
```json
|
||
{
|
||
".url": {
|
||
"havline.main": {
|
||
"title": "Havline",
|
||
"icon": "images/icon_{0}.png",
|
||
"type": "iframe",
|
||
"protocol": "http",
|
||
"port": "${wizard_service_port}",
|
||
"url": "/",
|
||
"allUsers": false
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
这是端口服务入口,不是统一网关入口。
|
||
|
||
### 4.3 当前前端路径假设
|
||
|
||
```text
|
||
Vue Router:
|
||
createWebHistory(),无 base
|
||
|
||
API 客户端:
|
||
直接使用 /api/... 绝对路径
|
||
|
||
SSE / EventSource:
|
||
/api/proxies/{id}/logs/stream
|
||
|
||
静态资源:
|
||
由 Vite 默认根路径构建
|
||
```
|
||
|
||
统一网关路径是 `/app/havline`,这些绝对路径不能直接复用。
|
||
|
||
### 4.4 当前容器权限
|
||
|
||
Dockerfile 没有显式 `USER`,容器默认以 root 运行。
|
||
|
||
这对创建 Socket 文件是有利的:
|
||
|
||
```text
|
||
/app/target/app.sock
|
||
```
|
||
|
||
但如果后续改为非 root 用户运行,需要额外处理 Socket 的属主和权限。
|
||
|
||
### 4.5 当前端口映射
|
||
|
||
```yaml
|
||
ports:
|
||
- "${wizard_service_port}:6893"
|
||
- "${wizard_nginx_http_port}:80"
|
||
- "${wizard_nginx_https_port}:443"
|
||
```
|
||
|
||
统一网关只替代管理界面的入口,不会替代 Havline 的反向代理端口。
|
||
|
||
## 五、目标架构
|
||
|
||
### 5.1 访问分层
|
||
|
||
```text
|
||
飞牛桌面
|
||
-> https://fnOS域名/app/havline
|
||
-> 飞牛统一网关校验登录态
|
||
-> /var/apps/havline/target/app.sock
|
||
-> Havline 网关监听器
|
||
-> Havline API / Web UI
|
||
|
||
Havline 公网反代功能
|
||
-> 18080/9443 端口
|
||
-> Havline 内置 Nginx
|
||
-> 用户配置的 NAS 服务
|
||
```
|
||
|
||
### 5.2 包结构目标
|
||
|
||
```text
|
||
fnos/havline/
|
||
├── manifest
|
||
├── app/
|
||
│ ├── docker/
|
||
│ │ └── docker-compose.yaml
|
||
│ └── ui/
|
||
│ └── config
|
||
├── config/
|
||
│ ├── privilege
|
||
│ └── resource
|
||
├── cmd/
|
||
│ ├── main
|
||
│ ├── install_callback
|
||
│ ├── upgrade_callback
|
||
│ ├── config_callback
|
||
│ └── uninstall_callback
|
||
└── wizard/
|
||
├── install
|
||
├── config
|
||
└── uninstall
|
||
```
|
||
|
||
### 5.3 Docker Compose 目标
|
||
|
||
```yaml
|
||
services:
|
||
havline:
|
||
image: jayhub.top/mrlee/havline:${TRIM_APPVER}-amd64-docker
|
||
container_name: havline
|
||
restart: unless-stopped
|
||
ports:
|
||
- "${wizard_nginx_http_port}:80"
|
||
- "${wizard_nginx_https_port}:443"
|
||
volumes:
|
||
- "${TRIM_PKGVAR}:/data"
|
||
- "${TRIM_APPDEST}:/app/target:rw"
|
||
environment:
|
||
HAVLINE_DATA_DIR: /data
|
||
HAVLINE_LISTEN: ":6893"
|
||
HAVLINE_GATEWAY_SOCKET: "/app/target/app.sock"
|
||
HAVLINE_GATEWAY_PREFIX: "/app/havline"
|
||
HAVLINE_TRUST_GATEWAY_HEADERS: "false"
|
||
HAVLINE_NGINX_HTTP_PORT: "80"
|
||
HAVLINE_NGINX_HTTPS_PORT: "443"
|
||
HAVLINE_UPDATER_SOCKET: ""
|
||
HAVLINE_INITIAL_ADMIN_PASSWORD: "${wizard_initial_admin_password}"
|
||
TZ: "${wizard_timezone}"
|
||
```
|
||
|
||
说明:
|
||
|
||
```text
|
||
HAVLINE_LISTEN 保留,用于端口回退和内部健康检查
|
||
HAVLINE_GATEWAY_SOCKET 指向统一网关 Socket
|
||
HAVLINE_GATEWAY_PREFIX 用于前端 base path 和路由
|
||
HAVLINE_TRUST_GATEWAY_HEADERS 第一阶段关闭
|
||
wizard_service_port 可以从端口映射中移除
|
||
```
|
||
|
||
### 5.4 manifest 目标
|
||
|
||
首版建议:
|
||
|
||
```ini
|
||
appname = havline
|
||
version = <版本>
|
||
display_name = Havline
|
||
desc = NAS 公网访问管理:反向代理、DDNS、HTTPS 证书、FRP、Cloudflare Tunnel 与一键升级
|
||
source = thirdparty
|
||
platform = all
|
||
maintainer = Havline
|
||
maintainer_url = https://jayhub.top/mrlee/Havline
|
||
distributor = Havline
|
||
desktop_uidir = ui
|
||
desktop_applaunchname = havline.main
|
||
checkport = false
|
||
ctl_stop = true
|
||
service_port = 6893
|
||
```
|
||
|
||
`service_port` 是否保留需要实测:
|
||
|
||
```text
|
||
如果只注册统一网关入口:
|
||
可以不依赖 service_port
|
||
|
||
如果同时保留端口回退入口:
|
||
保留 service_port 便于调试
|
||
```
|
||
|
||
## 六、三种实现路线
|
||
|
||
### 方案 A:Go 主程序原生监听 Unix Socket
|
||
|
||
实现:
|
||
|
||
```text
|
||
internal/config
|
||
增加 GatewaySocket、GatewayPrefix 配置
|
||
|
||
internal/app
|
||
同时创建 TCP listener 和 Unix listener
|
||
TCP 用于端口回退
|
||
Unix 用于统一网关
|
||
```
|
||
|
||
伪代码:
|
||
|
||
```go
|
||
tcpServer := &http.Server{Addr: cfg.ListenAddr, Handler: handler}
|
||
unixServer := &http.Server{Handler: handler}
|
||
|
||
if cfg.GatewaySocket != "" {
|
||
_ = os.Remove(cfg.GatewaySocket)
|
||
listener, err := net.Listen("unix", cfg.GatewaySocket)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if err := os.Chmod(cfg.GatewaySocket, 0o660); err != nil {
|
||
return err
|
||
}
|
||
go unixServer.Serve(listener)
|
||
}
|
||
|
||
go tcpServer.ListenAndServe()
|
||
```
|
||
|
||
优点:
|
||
|
||
```text
|
||
无额外进程
|
||
可以精确处理 base path
|
||
可以按来源区分 TCP 与网关请求
|
||
可以安全启用 X-Trim-* SSO
|
||
网络性能更好
|
||
```
|
||
|
||
缺点:
|
||
|
||
```text
|
||
需要修改 Go 主程序
|
||
需要补充 Unix Socket 生命周期测试
|
||
```
|
||
|
||
推荐度:高。
|
||
|
||
### 方案 B:容器内 Nginx 监听 Unix Socket
|
||
|
||
实现:
|
||
|
||
```text
|
||
容器内 Nginx 监听 /app/target/app.sock
|
||
反向代理到 127.0.0.1:6893
|
||
```
|
||
|
||
优点:
|
||
|
||
```text
|
||
不改 Go 主程序
|
||
可以快速做统一网关 PoC
|
||
```
|
||
|
||
缺点:
|
||
|
||
```text
|
||
多一层代理
|
||
base path 和 Header 处理复杂
|
||
错误排查链路更长
|
||
SSO 头部的可信边界不清晰
|
||
```
|
||
|
||
推荐度:中,适合过渡验证,不建议作为长期方案。
|
||
|
||
### 方案 C:单独 Sidecar 容器
|
||
|
||
实现:
|
||
|
||
```text
|
||
新增 sidecar 容器监听 Unix Socket
|
||
代理到 havline 容器
|
||
```
|
||
|
||
优点:
|
||
|
||
```text
|
||
主容器不增加进程
|
||
```
|
||
|
||
缺点:
|
||
|
||
```text
|
||
Compose 复杂度高
|
||
生命周期和升级耦合
|
||
收益不明显
|
||
```
|
||
|
||
推荐度:低。
|
||
|
||
## 七、前端 base path 改造
|
||
|
||
统一网关路径固定为:
|
||
|
||
```text
|
||
/app/havline
|
||
```
|
||
|
||
前端需要支持可配置 base path。
|
||
|
||
### 7.1 Vite
|
||
|
||
目标:
|
||
|
||
```ts
|
||
export default defineConfig({
|
||
base: process.env.VITE_BASE_PATH || '/',
|
||
// ...
|
||
})
|
||
```
|
||
|
||
构建飞牛统一网关版本时:
|
||
|
||
```text
|
||
VITE_BASE_PATH=/app/havline/
|
||
```
|
||
|
||
### 7.2 Vue Router
|
||
|
||
当前:
|
||
|
||
```ts
|
||
createWebHistory()
|
||
```
|
||
|
||
目标:
|
||
|
||
```ts
|
||
createWebHistory(import.meta.env.BASE_URL)
|
||
```
|
||
|
||
或者由运行时 `HAVLINE_GATEWAY_PREFIX` 注入。
|
||
|
||
### 7.3 API 客户端
|
||
|
||
当前:
|
||
|
||
```ts
|
||
fetch('/api/...')
|
||
```
|
||
|
||
目标:
|
||
|
||
```ts
|
||
const API_BASE = import.meta.env.BASE_URL.replace(/\/$/, '')
|
||
fetch(`${API_BASE}/api/...`)
|
||
```
|
||
|
||
或者由前端运行时配置读取:
|
||
|
||
```text
|
||
window.__HAVLINE_BASE_PATH__
|
||
```
|
||
|
||
### 7.4 SSE / EventSource
|
||
|
||
当前:
|
||
|
||
```ts
|
||
new EventSource('/api/proxies/{id}/logs/stream')
|
||
```
|
||
|
||
目标:
|
||
|
||
```ts
|
||
new EventSource(`${API_BASE}/api/proxies/{id}/logs/stream`)
|
||
```
|
||
|
||
### 7.5 WebSocket
|
||
|
||
目标路径:
|
||
|
||
```text
|
||
/app/havline/ws
|
||
```
|
||
|
||
前端必须使用当前页面主机和协议:
|
||
|
||
```ts
|
||
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:'
|
||
const wsUrl = `${wsProtocol}//${location.host}/app/havline/ws`
|
||
```
|
||
|
||
### 7.6 Cookie
|
||
|
||
需要确认 Havline 的会话 Cookie 是否设置了 Path。
|
||
|
||
建议:
|
||
|
||
```text
|
||
端口模式:Path=/
|
||
网关模式:Path=/app/havline
|
||
```
|
||
|
||
或者统一使用 `Path=/`,减少改造量。
|
||
|
||
### 7.7 CSRF / Origin
|
||
|
||
如果 Havline 有 Origin 或 CSRF 校验,需要把以下来源加入白名单:
|
||
|
||
```text
|
||
https://<fnOS域名>
|
||
https://<fnOS域名>/app/havline
|
||
```
|
||
|
||
## 八、认证与权限设计
|
||
|
||
### 8.1 第一阶段:双登录,低风险
|
||
|
||
```text
|
||
飞牛网关校验 fnOS 登录态
|
||
Havline 仍然显示自己的登录页
|
||
用户登录 Havline 后正常使用
|
||
```
|
||
|
||
优点:
|
||
|
||
```text
|
||
改造最小
|
||
不影响端口模式
|
||
不引入 SSO 安全问题
|
||
```
|
||
|
||
缺点:
|
||
|
||
```text
|
||
用户需要登录两次
|
||
```
|
||
|
||
建议首版采用。
|
||
|
||
### 8.2 第二阶段:网关 SSO
|
||
|
||
启用条件:
|
||
|
||
```text
|
||
HAVLINE_TRUST_GATEWAY_HEADERS=true
|
||
只有 Unix Socket 请求才信任 X-Trim-* Header
|
||
```
|
||
|
||
映射规则建议:
|
||
|
||
```text
|
||
X-Trim-Isadmin=true
|
||
-> 映射到 Havline admin
|
||
|
||
X-Trim-Isadmin=false
|
||
-> 默认拒绝管理界面
|
||
-> 或后续映射为只读用户
|
||
|
||
缺少 X-Trim-Userid
|
||
-> 视为非网关请求,回退到本地登录
|
||
```
|
||
|
||
安全要求:
|
||
|
||
```text
|
||
Unix Socket 文件权限限制为网关和 Havline 进程可访问
|
||
TCP 端口请求绝对不能信任 X-Trim-* Header
|
||
网关 Header 只用于身份上下文,不替代 Havline 业务权限
|
||
```
|
||
|
||
## 九、Havline 功能边界
|
||
|
||
统一网关只解决飞牛系统域名下的应用入口,不替代 Havline 的核心功能。
|
||
|
||
| Havline 功能 | 统一网关是否替代 | 说明 |
|
||
| --- | --- | --- |
|
||
| 管理界面入口 | 是 | 改为 `/app/havline` |
|
||
| fnOS 登录态 | 部分 | 网关校验登录,Havline 仍需业务鉴权 |
|
||
| 内置 Nginx 反向代理 | 否 | 仍由 Havline 管理 |
|
||
| DDNS | 否 | 仍由 Havline 管理 |
|
||
| HTTPS 证书申请 | 否 | 仍由 Havline 管理 |
|
||
| FRP 内网穿透 | 否 | 仍由 Havline 管理 |
|
||
| Cloudflare Tunnel | 否 | 仍由 Havline 管理 |
|
||
| 公网域名反代 | 否 | 仍由 Havline 管理 |
|
||
| 端口 18080/9443 | 否 | 作为 Havline 功能端口保留 |
|
||
|
||
不要把 Havline 的公网反代、证书和隧道功能迁入飞牛统一网关,否则会丢失
|
||
Havline 作为独立公网访问管理工具的能力。
|
||
|
||
## 十、生命周期与打包变更
|
||
|
||
### 10.1 安装回调
|
||
|
||
需要确保:
|
||
|
||
```text
|
||
${TRIM_APPDEST} 存在
|
||
${TRIM_APPDEST} 权限允许容器写入 app.sock
|
||
${TRIM_PKGVAR} 数据目录权限正确
|
||
```
|
||
|
||
### 10.2 启动
|
||
|
||
容器启动时:
|
||
|
||
```text
|
||
删除 ${TRIM_APPDEST}/app.sock 残留文件
|
||
创建新的 Unix Socket
|
||
设置 Socket 权限
|
||
```
|
||
|
||
### 10.3 升级
|
||
|
||
升级时:
|
||
|
||
```text
|
||
gatewayPrefix 保持不变
|
||
gatewaySocket 文件名保持不变
|
||
数据目录保持不变
|
||
Socket 文件重新创建
|
||
```
|
||
|
||
### 10.4 卸载
|
||
|
||
卸载时:
|
||
|
||
```text
|
||
删除 ${TRIM_APPDEST}/app.sock
|
||
默认保留 ${TRIM_PKGVAR}
|
||
```
|
||
|
||
## 十一、分阶段实施计划
|
||
|
||
### 阶段 0:官方行为 PoC
|
||
|
||
目标:用最小 Docker 示例验证统一网关。
|
||
|
||
必须验证:
|
||
|
||
```text
|
||
[ ] ${TRIM_APPDEST} 挂载路径
|
||
[ ] Socket 文件权限
|
||
[ ] /app/havline 请求是否保留前缀
|
||
[ ] Header 是否原样注入
|
||
[ ] WebSocket 是否可用
|
||
[ ] 应用重启后 Socket 是否自动重建
|
||
```
|
||
|
||
产出:
|
||
|
||
```text
|
||
docs/fnos-unified-gateway-poc.md
|
||
```
|
||
|
||
### 阶段 1:Havline 支持 Unix Socket
|
||
|
||
目标:主程序可以同时监听 TCP 和 Unix Socket。
|
||
|
||
改造:
|
||
|
||
```text
|
||
internal/config
|
||
internal/app
|
||
启动日志
|
||
停止清理
|
||
```
|
||
|
||
验收:
|
||
|
||
```text
|
||
[ ] TCP 6893 正常
|
||
[ ] Unix Socket 正常
|
||
[ ] Socket 权限正确
|
||
[ ] 容器重启后 Socket 重建
|
||
```
|
||
|
||
### 阶段 2:前端 base path
|
||
|
||
目标:Havline 可以在 `/app/havline` 下完整运行。
|
||
|
||
改造:
|
||
|
||
```text
|
||
Vite base
|
||
Vue Router base
|
||
API client base
|
||
EventSource base
|
||
WebSocket base
|
||
Cookie Path
|
||
CSRF / Origin
|
||
```
|
||
|
||
验收:
|
||
|
||
```text
|
||
[ ] 页面打开
|
||
[ ] 静态资源加载
|
||
[ ] 登录
|
||
[ ] API
|
||
[ ] SSE
|
||
[ ] WebSocket
|
||
[ ] 刷新深层路由
|
||
```
|
||
|
||
### 阶段 3:飞牛包切换统一网关入口
|
||
|
||
目标:桌面入口走 `/app/havline`。
|
||
|
||
改造:
|
||
|
||
```text
|
||
app/ui/config
|
||
docker-compose.yaml
|
||
```
|
||
|
||
保留:
|
||
|
||
```text
|
||
18080/9443 端口
|
||
端口回退入口
|
||
```
|
||
|
||
验收:
|
||
|
||
```text
|
||
[ ] 飞牛桌面入口打开正确
|
||
[ ] 不需要端口入口即可访问管理界面
|
||
[ ] 18080/9443 功能不受影响
|
||
```
|
||
|
||
### 阶段 4:网关 SSO
|
||
|
||
目标:使用 `X-Trim-*` 自动识别飞牛管理员。
|
||
|
||
改造:
|
||
|
||
```text
|
||
HAVLINE_TRUST_GATEWAY_HEADERS=true
|
||
只对 Unix Socket 请求生效
|
||
管理员映射
|
||
非管理员处理策略
|
||
```
|
||
|
||
验收:
|
||
|
||
```text
|
||
[ ] 管理员免登录
|
||
[ ] 非管理员被正确拒绝或只读
|
||
[ ] TCP 请求伪造 Header 无效
|
||
```
|
||
|
||
### 阶段 5:体验收敛
|
||
|
||
目标:统一网关成为默认入口,端口仅保留给 Havline 自身功能。
|
||
|
||
验收:
|
||
|
||
```text
|
||
[ ] 飞牛桌面入口只显示统一网关入口
|
||
[ ] 端口入口不再出现在应用卡片
|
||
[ ] 升级、卸载、重装正常
|
||
```
|
||
|
||
## 十二、验收清单
|
||
|
||
```text
|
||
[ ] gatewayPrefix 稳定为 /app/havline
|
||
[ ] gatewaySocket 稳定为 app.sock
|
||
[ ] ${TRIM_APPDEST} 正确挂载
|
||
[ ] Havline 监听 Unix Socket
|
||
[ ] Socket 启动前清理残留文件
|
||
[ ] Socket 权限满足网关访问
|
||
[ ] 前端使用 /app/havline base
|
||
[ ] API、SSE、WebSocket 路径正确
|
||
[ ] Cookie 和 CSRF 正常
|
||
[ ] 端口回退仍可用
|
||
[ ] 飞牛管理员可通过网关访问
|
||
[ ] 非管理员访问策略明确
|
||
[ ] 升级后数据不丢
|
||
[ ] 卸载默认保留数据
|
||
[ ] amd64 与 arm64 镜像可启动
|
||
```
|
||
|
||
## 十三、风险与未决问题
|
||
|
||
| 风险 | 影响 | 处理 |
|
||
| --- | --- | --- |
|
||
| 网关是否剥离 `/app/havline` 前缀未知 | 前端 base path 可能不一致 | 阶段 0 PoC 实测 |
|
||
| Socket 属主和权限未知 | 网关无法访问 Socket | PoC 验证并在启动时 chmod |
|
||
| 前端绝对路径较多 | 网关下页面或 API 404 | 统一引入 base path |
|
||
| SSE / WebSocket 路径 | 实时日志和状态页不可用 | 阶段 2 专项测试 |
|
||
| 双登录体验 | 用户需要登录两次 | 阶段 4 再做 SSO |
|
||
| 伪造 `X-Trim-*` Header | 越权风险 | 只信任 Unix Socket 请求 |
|
||
| 端口与网关并存 | 入口混乱 | 桌面只注册网关入口 |
|
||
| Havline 公网反代功能边界 | 用户误解为被替代 | 文档明确说明 |
|
||
| 飞牛审核要求 | 上架被拒 | 提交前复核官方审核项 |
|
||
| 镜像多架构兼容 | 飞牛 Docker 28.5.2 对 OCI index 兼容性有限 | 使用 Docker schema2 兼容格式 |
|
||
|
||
## 十四、推荐落地顺序
|
||
|
||
```text
|
||
1. 做最小统一网关 Docker PoC
|
||
2. 确认前缀、Socket 权限、Header、WebSocket
|
||
3. 主程序增加 Unix Socket 监听
|
||
4. 前端增加可配置 base path
|
||
5. 飞牛包注册统一网关入口
|
||
6. 保留端口回退
|
||
7. 实测通过后再做网关 SSO
|
||
8. 最后收敛桌面入口
|
||
```
|
||
|
||
## 十五、结论
|
||
|
||
Havline 不需要从 Docker 应用模板重写成另一种应用,而应在现有 Docker
|
||
应用上叠加统一网关访问模型。
|
||
|
||
推荐的最终形态:
|
||
|
||
```text
|
||
运行形态:飞牛 Docker 应用
|
||
访问模型:统一网关 /app/havline
|
||
服务监听:TCP 6893 + Unix Socket app.sock
|
||
用户体系:首版保留 Havline 登录,后续再做网关 SSO
|
||
功能边界:Havline 继续管理公网反代、DDNS、证书、FRP 和 Cloudflare Tunnel
|
||
回退能力:保留 18080/9443 端口
|
||
```
|
||
|
||
这样既能获得飞牛系统统一的 HTTPS 域名、登录态和桌面入口体验,又不会
|
||
破坏 Havline 作为独立 NAS 公网访问管理工具的核心能力。
|