17 KiB
Havline 飞牛 fnOS 应用打包方案
日期:2026-10-01 依据:飞牛应用开放平台文档:https://developer.fnnas.com/docs/guide/ 目标:将 Havline 从 Docker Compose / 裸机部署,改造为可安装、可升级、可卸载的飞牛 fnOS
.fpk应用。
一、官方文档结论
飞牛 fnOS 应用包的核心组成:
manifest
config/privilege
config/resource
app/
cmd/
wizard/
ICON.PNG
ICON_256.PNG
官方提供三种常见形态:
- CGI 应用
- 适合静态页面和轻量入口。
- 不支持 WebSocket,不适合 Havline 这种常驻服务。
- 统一网关应用
- 复用 fnOS 访问域名和登录态。
- 服务监听 Unix Socket,由 fnOS 网关转发。
- 支持 WebSocket,但 Havline 需要新增 Unix Socket 监听能力。
- Docker 应用
- 使用
app/docker/docker-compose.yaml。 - 在
config/resource中声明docker-project。 - 最适合 Havline 当前形态。
- 使用
首选方案:飞牛 Docker 应用模板。
原因:
- Havline 已经是完整 Docker 应用。
- 镜像内已包含 Nginx、Go 主程序和数据库运行环境。
- Cloudflare Tunnel、FRP、Agent、证书管理都依赖常驻进程。
- fnOS Docker 项目资源可以托管启动、停止和运行状态。
- 避免把 Havline 的 Go、Nginx、SQLite 和 shell 依赖重新拆进原生应用生命周期。
二、当前 Havline 的部署形态
2.1 当前主要组成
havline
├── Go 主程序
├── 内嵌 Web 前端
├── 内置 Nginx
├── SQLite
├── FRP 客户端 / Agent 管理
├── Cloudflare Tunnel / cloudflared
├── ACME / DDNS / 通知
└── 可选 havline-updater sidecar
2.2 当前关键目录
/data
├── havline.db
├── session_secret
├── nginx/
├── certs/
├── logs/
├── frp/
└── cloudflared/
2.3 当前端口
| 端口 | 用途 |
|---|---|
6893 |
Havline 管理界面/API |
80 |
本机 Nginx HTTP |
443 |
本机 Nginx HTTPS |
飞牛 fnOS 通常已占用 80/443,因此 fnOS 包应默认使用:
管理端口:6893
Nginx HTTP:18080
Nginx HTTPS:9443
端口应允许安装向导修改。
2.4 当前 updater 的限制
Havline 当前的一键升级依赖:
- Docker Socket
- updater sidecar
- 宿主机 Compose 项目目录
- 对宿主 Docker 执行
compose build/up的权限
在 fnOS 应用包内继续保留这条链路会带来:
- 容器内 path mapping 复杂;
- fnOS 应用目录路径和宿主路径不一致;
- 应用中心升级与 Havline 内部升级冲突;
- 需要给应用额外 Docker Socket 权限。
方案:fnOS 首发版本默认关闭 Havline 内置 updater,统一由飞牛应用中心升级。
后续如果需要,可做“高级模式”单独启用。
三、目标包结构
建议在仓库新增:
fnos/
├── manifest
├── ICON.PNG
├── ICON_256.PNG
├── config/
│ ├── privilege
│ └── resource
├── app/
│ ├── docker/
│ │ └── docker-compose.yaml
│ └── ui/
│ ├── config
│ └── images/
│ ├── icon_64.png
│ └── icon_256.png
├── cmd/
│ ├── main
│ ├── install_init
│ ├── install_callback
│ ├── upgrade_init
│ ├── upgrade_callback
│ ├── uninstall_init
│ ├── uninstall_callback
│ ├── config_init
│ └── config_callback
└── wizard/
├── install
├── upgrade
├── config
└── uninstall
构建脚本负责:
- 复制
fnos/模板到临时目录。 - 用
VERSION替换manifest.version。 - 生成对应的
docker-compose.yaml。 - 调用
fnpack build。 - 输出
havline-<version>.fpk。
四、Manifest 草案
appname=havline
version=1.9.0
display_name=Havline
desc=NAS 公网访问管理:反向代理、FRP、Cloudflare Tunnel、DDNS 与证书
source=thirdparty
platform=all
maintainer=Havline
maintainer_url=https://example.com
os_min_version=1.0.0
desktop_uidir=ui
desktop_applaunchname=havline.main
service_port=6893
checkport=true
ctl_stop=true
changelog=本次更新
字段说明:
appname=havline:应用唯一标识。platform=all:包内不含架构二进制,实际架构由 multi-arch 镜像决定。service_port=6893:桌面入口默认访问端口。ctl_stop=true:应用中心显示启动、停止和运行状态。desktop_applaunchname=havline.main:应用卡片默认入口。
多架构要求
发布时必须提供:
linux/amd64
linux/arm64
否则 platform=all 只在镜像本身支持目标架构时成立。
五、权限与资源
5.1 config/privilege
{
"defaults": {
"run-as": "package"
},
"username": "havline",
"groupname": "havline"
}
原则:
- 长期运行的主程序使用包用户。
- 不默认给 Root。
- 不默认挂载 Docker Socket。
- 不默认加入
docker用户组。
如果未来需要启用内部 updater,应单独评估 Docker Socket 的权限边界。
5.2 config/resource
{
"docker-project": {
"projects": [
{
"name": "havline",
"path": "docker"
}
]
}
}
可选共享目录:
{
"data-share": {
"shares": [
{
"name": "havline/backups"
}
]
}
}
建议首发版本只暴露备份目录。
不要暴露:
havline.dbsession_secretcerts/nginx/cloudflared/frp/
这些内容包含访问控制、证书和加密凭据。
六、Docker Compose 草案
建议新建:
fnos/app/docker/docker-compose.yaml
内容:
name: havline
services:
havline:
image: ghcr.io/example/havline:${TRIM_APPVER}
container_name: havline
restart: unless-stopped
ports:
- "${TRIM_SERVICE_PORT}:6893"
- "${wizard_nginx_http_port}:18080"
- "${wizard_nginx_https_port}:9443"
volumes:
- "${TRIM_PKGVAR}/data:/data"
environment:
HAVLINE_DATA_DIR: /data
HAVLINE_LISTEN: ":6893"
HAVLINE_NGINX_HTTP_PORT: "${wizard_nginx_http_port}"
HAVLINE_NGINX_HTTPS_PORT: "${wizard_nginx_https_port}"
HAVLINE_SESSION_SECRET: ""
TZ: "${wizard_timezone}"
HAVLINE_UPDATER_SOCKET: ""
说明:
TRIM_SERVICE_PORT默认映射管理界面。- 使用 bridge 网络,避免与 fnOS 现有 host 网络服务直接冲突。
- Nginx 使用 18080/9443,避免占用 fnOS 的 80/443。
- 数据全部落在
TRIM_PKGVAR/data。 - 不挂载 Docker Socket。
- 不启动 updater sidecar。
为什么首发不直接用 host 网络
host 网络确实更贴近 Havline 裸机模式,但:
- fnOS 本身和其他应用可能占用大量端口;
- 应用中心无法明确管理端口冲突;
- bridge + 端口映射更容易解释和回滚。
首发建议 bridge。
后续可提供“高级安装模式:host 网络”。
七、桌面入口
app/ui/config:
{
".url": {
"havline.main": {
"title": "Havline",
"icon": "images/icon_{0}.png",
"type": "iframe",
"protocol": "http",
"port": "${TRIM_SERVICE_PORT}",
"url": "/",
"allUsers": false,
"control": {
"accessPerm": "readonly"
}
}
}
}
考虑:
- Havline 当前是单管理员产品,默认只让管理员可见。
- 使用端口服务,不依赖 fnOS 网关。
- 后续若新增统一网关,可把入口改成
gatewayPrefix/gatewaySocket,无需端口。
八、生命周期脚本
cmd/main
Docker 项目的启停由 fnOS Docker 资源处理,main 主要提供状态检查。
#!/bin/bash
CONTAINER_NAME="havline"
is_running() {
docker inspect "$CONTAINER_NAME" 2>/dev/null | grep -q '"Status": "running"'
}
case "$1" in
start|stop)
exit 0
;;
status)
if is_running; then
exit 0
fi
exit 3
;;
*)
echo "Unknown command: $1" > "$TRIM_TEMP_LOGFILE"
exit 1
;;
esac
cmd/install_callback
职责:
- 创建
TRIM_PKGVAR/data - 修正目录权限
- 如果用户选择了迁移,执行数据复制
- 输出安装摘要
cmd/upgrade_callback
职责:
- 检查数据库可读写
- 保留已有的
session_secret - 记录升级前后版本
- 可选:导入旧版本迁移文件
cmd/config_callback
职责:
- 配置端口或时区变化时,验证新端口
- 如果只是环境变量变化,尽量交给 Docker Compose 重建
- 输出清晰的失败原因到
TRIM_TEMP_LOGFILE
cmd/uninstall_callback
默认行为:
- 删除容器和程序
- 保留应用数据
只有用户在卸载向导中明确选择“删除全部数据”时,才删除:
${TRIM_PKGVAR}/data
九、安装向导
wizard/install
建议字段:
[
{
"stepTitle": "Havline 访问端口",
"items": [
{
"type": "text",
"field": "wizard_nginx_http_port",
"label": "Nginx HTTP 端口",
"initValue": "18080",
"rules": [
{
"required": true,
"message": "请输入端口"
},
{
"pattern": "^[0-9]+$",
"message": "端口必须是数字"
}
]
},
{
"type": "text",
"field": "wizard_nginx_https_port",
"label": "Nginx HTTPS 端口",
"initValue": "9443",
"rules": [
{
"required": true,
"message": "请输入端口"
},
{
"pattern": "^[0-9]+$",
"message": "端口必须是数字"
}
]
}
]
},
{
"stepTitle": "基础设置",
"items": [
{
"type": "select",
"field": "wizard_timezone",
"label": "时区",
"initValue": "Asia/Shanghai",
"options": [
{
"label": "Asia/Shanghai",
"value": "Asia/Shanghai"
},
{
"label": "UTC",
"value": "UTC"
}
]
}
]
}
]
管理员初始化
当前 Havline 首次启动会自动生成随机管理员密码并写入日志。
对于 fnOS 应用,建议改造为:
- 安装向导收集初始管理员密码;或
- 首次打开 Havline 时进入创建管理员页面;或
- 读取
HAVLINE_INITIAL_ADMIN_PASSWORD进行一次性初始化。
不建议继续把管理员密码写入日志。
十、环境变量映射
| fnOS 变量 | 映射到 Havline | 说明 |
|---|---|---|
TRIM_APPVER |
镜像 tag | 应用版本 |
TRIM_SERVICE_PORT |
6893 映射 | 桌面入口 |
TRIM_PKGVAR |
/data |
持久化数据 |
TRIM_APPDEST |
应用 target | 只读程序目录 |
wizard_nginx_http_port |
HAVLINE_NGINX_HTTP_PORT |
Nginx HTTP |
wizard_nginx_https_port |
HAVLINE_NGINX_HTTPS_PORT |
Nginx HTTPS |
wizard_timezone |
TZ |
时区 |
Havline 数据目录内应继续保留:
${TRIM_PKGVAR}/data/session_secret
不要每次安装或升级重新生成。
十一、更新策略
首发方案
统一使用飞牛应用中心升级:
- 更新
.fpk - 拉取新 multi-arch 镜像
- 保留
${TRIM_PKGVAR}/data - 重启 Docker Compose 项目
禁用 Havline 自带 updater
fnOS 包内:
HAVLINE_UPDATER_SOCKET=""
并且 compose 不启动:
havline-updater
原因:
- App Center 已经是唯一更新入口。
- 避免 Docker Socket 权限。
- 避免应用目录映射错误。
- 避免升级 Recursive / 自更新冲突。
后续可选
如果确实需要应用内更新,再增加“高级更新模式”:
- 显式让用户开启;
- 显式挂载 Docker Socket;
- 显式配置宿主项目路径;
- 失败时回滚到上一版镜像。
十二、数据迁移方案
从 Docker Compose 迁移
旧环境可能有:
/opt/havline/data
或 Docker 命名卷:
havline-data
迁移步骤:
- 停止旧 Havline。
- 备份旧数据目录。
- 将旧目录复制到:
${TRIM_PKGVAR}/data
- 确认
session_secret和havline.db都存在。 - 启动 fnOS 应用。
- 登录并验证:
- 用户与设置
- 反向代理规则
- FRP 服务端
- Cloudflare 隧道
- 证书
- 备份列表
从裸机迁移
裸机默认数据目录通常是:
./.data
迁移内容一致,重点是保持 session_secret 不变。
否则:
- 已加密的 API Token
- DDNS 凭据
- 通知凭据
- Cloudflare 凭据
可能无法解密。
十三、安全设计
默认安全基线
run-as=package- 不启用 Root
- 不挂载 Docker Socket
- 不暴露证书、数据库和加密密钥目录
- 数据目录仅应用用户可读写
- 仅暴露管理端口、Nginx 高位的 HTTP/HTTPS 端口
- 管理员入口默认只对 fnOS 管理员可见
首次管理员密码
必须改造当前“随机密码写日志”行为。
推荐优先级:
- 首次进入创建管理员页面
- 安装向导收集密码
- 环境变量一次性初始化
Cloudflare Token
仍由 Havline 独立加密保存:
${TRIM_PKGVAR}/data/session_secret
不放入 fnOS 共享目录。
十四、构建与发布流程
1. 构建 multi-arch 镜像
docker buildx build \
--platform linux/amd64,linux/arm64 \
--build-arg VERSION_FILE=VERSION \
-t registry.example.com/havline:${VERSION} \
--push .
2. 生成 fnOS 项目
从以下模板生成:
fnos/
替换:
manifest.version- Compose 镜像 tag
- 应用说明
changelog
3. 打包
fnpack build --directory dist/fnos/havline
4. 测试设备安装
appcenter-cli install-fpk havline-${VERSION}.fpk --env config.env
5. 发布前验证
在至少一台 x86 和一台 ARM 测试设备验证:
- 安装
- 启动
- 桌面入口
- 端口映射
- 数据持久化
- 应用中心升级
- 卸载保留数据
- 卸载删除数据
十五、分阶段实施
阶段 1:可用包
- 新增
fnos/目录 - 编写 manifest
- Docker Compose 模板
- 安装/卸载/状态生命周期
- 管理入口
- 端口向导
- 使用固定镜像 tag
目标:可以在飞牛测试设备安装并打开 Havline。
阶段 2:数据与升级
TRIM_PKGVAR持久化- 旧 Docker Compose 数据迁移脚本
- 应用中心升级验证
- 保留/删除数据选项
session_secret兼容检查
目标:数据不丢,可稳定升级。
阶段 3:fnOS 体验
- 首次管理员初始化页面
- 端口冲突检测
- 应用入口图标完善
- 备份目录共享
- 时区和系统信息适配
目标:符合飞牛应用中心体验。
阶段 4:高级集成
- 可选统一网关
- 可选 Docker Socket updater
- fnOS 开放 API 集成
- 应用中心审核材料
- ARM 实机完整验证
十六、风险与处理
| 风险 | 影响 | 处理 |
|---|---|---|
80/443 被 fnOS 占用 |
Havline Nginx 无法启动 | 默认改用 18080/9443,向导可改 |
| 镜像只有 amd64 | ARM 设备无法运行 | CI 构建 amd64 + arm64 |
| fnOS 应用升级与内置 updater 冲突 | 版本回退或容器失控 | 首发禁用内置 updater |
session_secret 丢失 |
加密凭据无法解密 | 持久化到 TRIM_PKGVAR,迁移时必须保留 |
| Docker Socket 权限过大 | 安全风险 | 默认不挂载 |
| 数据目录权限错误 | 数据库无法写入 | install_callback 修正权限 |
| 管理员密码写日志 | 凭据泄露 | 安装向导或首次设置页 |
| 端口冲突 | 应用无法启动 | 向导配置,安装时检测 |
| ARM 镜像无 cloudflared | Cloudflare 隧道不可用 | 镜像内校验架构,或运行时下载对应 binary |
十七、官方文档参考
- 欢迎加入:https://developer.fnnas.com/docs/guide/
- 快速开始:
- 核心概念:
- https://developer.fnnas.com/docs/core-concepts/manifest/
- https://developer.fnnas.com/docs/core-concepts/privilege/
- https://developer.fnnas.com/docs/core-concepts/resource/
- https://developer.fnnas.com/docs/core-concepts/runtime/
- https://developer.fnnas.com/docs/core-concepts/environment-variables/
- https://developer.fnnas.com/docs/core-concepts/app-entry/
- https://developer.fnnas.com/docs/core-concepts/gateway-registration/
- https://developer.fnnas.com/docs/core-concepts/wizard/
- 工具:
- Docker 案例:
十八、结论
Havline 适合做飞牛 Docker 应用,不适合首发做成 CGI 或原生应用。
首发目标:
飞牛应用中心安装
→ Docker Compose 启动 Havline
→ 桌面入口打开管理界面
→ 数据持久化到 TRIM_PKGVAR
→ 应用中心升级
最需要先解决的代码问题:
- 管理员初始化不再写日志。
- 默认 updater 改为可关闭,fnOS 包中关闭。
- 增加 multi-arch 镜像发布。
- 增加
fnos/打包模板和构建脚本。 - 验证
TRIM_PKGVAR/data数据迁移。