讲师中心 微信公众号
AI工具推荐 视频效率加速

Fn Fpk

Polar Sponsor
爱发电 赞助
.NET 9.0

飞牛NAS FPK 应用打包开发技能,用于开发和打包飞牛NAS第三方应用(.fpk),支持原生(Node.js、Python、Java、Go、Shell)和 Docker 应用,覆盖开发环境准备、fnpack 项目创建、manifest 配置、权限与资源配置等全流程。

fn-fpk — 飞牛 NAS fnOS FPK 应用开发

功能概述

fn-fpk — 飞牛 NAS fnOS FPK 应用开发是一项面向实际任务的技能,主要用于基于官方文档 https://developer.fnnas.com 于 2026-06-02 全面更新;系统架构: x86_64, Linux 内核 6.12.18+, Debian 发行版;

核心要点

  • fnpack 版本: 1.2.1, appcenter-cli 预装在 fnOS 中;
  • 1. 开发环境准备;
  • 1.1 系统要求。

使用与执行

它将相关步骤、工具调用和结果整理方式集中到统一流程中,帮助使用者更快完成目标并减少重复操作。从功能定位来看,该技能强调把分散的操作要求整理成清晰、可复用的处理流程,使用户能够围绕既定目标快速准备输入、选择执行方式并获得结构化结果。实际使用前应先确认任务范围、数据来源、运行环境、必要权限和关键参数,再依据技能说明逐步执行;若输入条件不完整,应先补齐信息或采用保守配置,避免因错误假设导致结果偏离需求。

结果检查与注意事项

执行过程中需要关注工具调用是否成功、接口或依赖是否可用、输出格式是否符合预期,并对异常提示、缺失字段和边界情况进行处理;涉及批量任务时,还应保存进度,避免中断后重复操作。该技能适合用于一次性任务,也可以接入自动化工作流,与其他技能或上层代理配合完成更完整的业务链路;在组合使用时,应明确每一步的输入输出关系,并避免不同步骤之间出现参数冲突。

fn-fpk — 飞牛 NAS fnOS FPK 应用开发

基于官方文档 https://developer.fnnas.com 于 2026-06-02 全面更新。 系统架构: x86_64, Linux 内核 6.12.18+, Debian 发行版。 fnpack 版本: 1.2.1, appcenter-cli 预装在 fnOS 中。

1. 开发环境准备

1.1 系统要求

  • fnOS 版本: ≥ 0.9.27
  • 架构: 仅支持 x86_64 (AMD64) — 应用的编译选项也需选择 x86_64
  • 存储: 至少创建一个存储空间
  • 权限: 管理员权限(安装/卸载/系统设置)

1.2 开发方式

  • 本地开发: 在本地用 fnpack CLI 打包,再传送到 fnOS 设备安装
  • 远程开发: SSH 到 fnOS 设备直接开发,结合 appcenter-cli install-local 快速测试

1.3 技术栈

语言/框架 支持情况
Node.js ✅ (v14/v16/v18/v20/v22)
Python ✅ (3.8/3.9/3.10/3.11/3.12)
Java ✅ (11/17/21 OpenJDK)
Go ✅ Linux 运行时支持
Shell ✅ (Bash)
HTML/JS/CSS ✅ 前端任意框架

1.4 CLI 工具

fnpack 打包工具

下载地址:https://static2.fnnas.com/fnpack/fnpack-1.2.1-{os}-{arch}

平台 二进制
Windows x86 fnpack-1.2.1-windows-amd64
Linux x86 fnpack-1.2.1-linux-amd64
Linux ARM fnpack-1.2.1-linux-arm64
macOS Intel fnpack-1.2.1-darwin-amd64
macOS M 系列 fnpack-1.2.1-darwin-arm64

安装:

chmod +x fnpack-1.2.1-linux-amd64
sudo mv fnpack-1.2.1-linux-amd64 /usr/local/bin/fnpack
fnpack --help

appcenter-cli (预装在 fnOS 中)

# 安装 fpk 文件
appcenter-cli install-fpk myapp.fpk

# 从本地目录安装(开发模式,无需打包)
cd /path/to/myapp
appcenter-cli install-local

# 管理应用
appcenter-cli list              # 已安装列表
appcenter-cli start myapp       # 启动
appcenter-cli stop myapp        # 停止

# 手动安装功能(仅测试用途)
appcenter-cli manual-install             # 查看状态
appcenter-cli manual-install enable      # 开启
appcenter-cli manual-install disable     # 关闭

# 设置默认存储空间
appcenter-cli default-volume             # 查看当前
appcenter-cli default-volume 1           # 设置存储空间1

# 静默安装(跳过向导)
appcenter-cli install-fpk myapp.fpk --env config.env

2. 应用类型

类型 描述 创建命令
Native 应用 直接运行在 fnOS 上的应用 fnpack create
Docker 应用 基于 Docker Compose 容器编排 fnpack create --template docker
纯服务应用(无 UI) 无 Web 访问入口 加 --without-ui true

3. 项目结构与核心文件

3.1 通用结构(fnpack create 生成)

myapp/
├── app/                      # 应用可执行文件/资源目录
│   ├── server/               # 后台服务程序(Native 应用)
│   ├── ui/                   # Web UI 入口配置
│   │   ├── images/           # 入口图标(icon_64.png, icon_256.png)
│   │   └── config            # 入口配置文件(JSON)
│   ├── www/                  # Web 静态资源(HTML/CSS/JS)
│   └── docker/               # Docker Compose 文件(Docker 应用)
│       └── docker-compose.yaml
├── manifest                  # 应用基本信息(必需)
├── cmd/                      # 生命周期管理脚本(全部必需)
│   ├── main                  # 启动/停止/状态检查
│   ├── install_init          # 安装前初始化
│   ├── install_callback      # 安装后回调
│   ├── uninstall_init        # 卸载前
│   ├── uninstall_callback    # 卸载后
│   ├── upgrade_init          # 升级前
│   ├── upgrade_callback      # 升级后
│   ├── config_init           # 配置变更前
│   └── config_callback       # 配置变更后
├── config/
│   ├── privilege             # 权限配置(JSON,必需)
│   └── resource              # 资源配置(JSON,必需)
├── wizard/                   # 向导配置(可选)
│   ├── install               # 安装向导
│   ├── uninstall             # 卸载向导
│   ├── upgrade               # 更新向导
│   └── config                # 配置向导
├── ICON.PNG                  # 64×64 应用图标(必需)
├── ICON_256.PNG              # 256×256 应用图标(必需)
└── LICENSE                   # 许可证(可选)

3.2 安装后的目录结构

当应用安装到 fnOS 后,系统创建如下目录:

/var/apps/[appname]/
├── cmd/                      # 生命周期脚本(来自包)
├── config/
│   ├── privilege             # 权限配置
│   └── resource              # 资源配置
├── ICON_256.PNG
├── ICON.PNG
├── LICENSE
├── manifest
├── etc -> /vol{volume}/@appconf/[appname]     # 静态配置文件
├── home -> /vol{volume}/@apphome/[appname]    # 用户数据
├── target -> /vol{volume}/@appcenter/[appname] # 可执行文件
├── tmp -> /vol{volume}/@apptemp/[appname]     # 临时文件
├── var -> /vol{volume}/@appdata/[appname]     # 运行时数据
├── shares/                   # 共享数据目录(按 resource 配置)
│   ├── datashare1 -> /vol{volume}/@appshare/datashare1
│   └── datashare2 -> /vol{volume}/@appshare/datashare2
└── wizard/                   # 向导配置(安装/卸载/升级/配置)
    ├── install
    ├── uninstall
    ├── upgrade
    └── config

4. manifest — 应用基本信息

manifest 文件是应用的"身份证",放在项目根目录,无扩展名。

4.1 完整字段参考

# ═══════════════ 基本信息 ═══════════════
appname        = myapp                        # ① 应用唯一标识,系统全局唯一
version        = 1.0.0                        # ② 版本号:x[.y[.z]][-build]
display_name   = 我的应用                      # ③ 用户可见的名称
desc           = 这是一个示例应用              # ④ 详细介绍,支持 HTML 格式
source         = thirdparty                   # ⑤ 固定值:thirdparty

# ═══════════════ 系统要求 ═══════════════
platform       = x86                          # ⑥ 架构:x86 | arm | all (V1.1.8+)
arch           = x86_64                       # ⑦ 已废弃,请用 platform
os_min_version = 0.9.0                        # ⑧ 最低系统版本
os_max_version = 0.9.100                      # ⑨ 最高系统版本

# ═══════════════ 开发者信息 ═══════════════
maintainer     = 张三                          # 开发者/团队名称
maintainer_url = https://example.com           # 开发者网站
distributor    = 示例公司                      # 发布者
distributor_url = https://company.com          # 发布者网站

# ═══════════════ 安装与运行控制 ═══════════════
install_type   =                              # 安装位置:空=用户可选存储空间,root=系统分区
ctl_stop       = true                         # 是否显示启动/停止按钮,默认 true
checkport      = true                         # 是否启用端口检查,默认 true
service_port   = 8080                         # 应用监听端口(单个端口)
disable_authorization_path = false            # 是否禁用授权目录功能

# ═══════════════ 用户界面 ═══════════════
desktop_uidir          = ui                   # UI 组件目录(相对应用根目录)
desktop_applaunchname  = myapp.Application    # 默认启动入口 ID

# ═══════════════ 依赖管理 ═══════════════
install_dep_apps = mariaDB:redis              # 依赖应用列表,格式:app1>2.2.2:app2:app3

# ═══════════════ 应用更新 ═══════════════
changelog = 新增了XX功能                       # 更新日志(升级时展示)

4.2 字段详解

应用标识

字段 必填 说明
appname ✅ 全局唯一标识符,用于系统识别
version ✅ 格式:x[.y[.z]][-build],例如 1.0.0、2.1.3-beta
display_name ✅ 应用中心显示的名称
desc ✅ 详细介绍,支持 HTML 格式
source ✅ 固定为 thirdparty

系统要求(V1.1.8+ 新增 platform 字段)

字段 说明
platform = x86 仅支持 x86 架构
platform = arm 仅支持 arm 架构
platform = all 所有架构,Docker 应用常用
arch 【已废弃】用 platform 替代

安装控制

字段 默认值 说明
install_type 空 root = 安装到系统分区 /usr/local/apps/@appcenter/;空 = 用户选择存储位置
ctl_stop true false 时隐藏启动/停止按钮和运行状态(无进程应用)
checkport true false 时系统不检查端口占用
service_port — 应用监听端口(仅支持单个端口)
disable_authorization_path false true 时应用设置页不显示授权目录操作

依赖管理

# 格式:app1>2.2.2:app2:app3
# > 表示最低版本要求
# : 分隔多个依赖
# 系统按列表顺序自动安装依赖
install_dep_apps = mariaDB:redis

5. 权限配置 (config/privilege)

config/privilege 文件定义应用运行时的权限级别和用户身份,JSON 格式,必需。

5.1 默认权限模式(推荐)

{
  "defaults": {
    "run-as": "package"
  },
  "username": "myapp_user",
  "groupname": "myapp_group"
}
字段 默认值 说明
run-as package package = 应用用户模式,root = root 模式
username appname 应用专用用户名
groupname appname 应用专用用户组名

默认模式行为

  • 系统为应用创建专用用户和用户组
  • 所有应用进程以专用用户身份运行
  • 应用文件所有者是该专用用户
  • 应用只能访问自己的目录和系统允许的公共资源

5.2 Root 权限模式

⚠️ 仅飞牛官方合作的企业开发者可使用,第三方应用默认无法在应用中心发布 root 权限应用。

{
  "defaults": {
    "run-as": "root"
  },
  "username": "myapp_user",
  "groupname": "myapp_group"
}

Root 模式行为

  • 应用脚本以 root 身份执行
  • 应用进程可以 root 身份或指定应用用户身份运行
  • 应用文件所有者变为 root
  • 系统仍会创建应用专用用户和用户组

5.3 外部文件访问权限

应用默认无法访问用户个人文件。用户需要在应用设置中授权:

  • 读写权限:读取和修改文件
  • 只读权限:只能读取,不能修改
  • 禁止访问:无法访问该路径

也可通过 config/resource 的 data-share 设置默认共享目录。

5.4 当前用户检查

echo "当前运行用户: $TRIM_RUN_USERNAME"
echo "应用专用用户: $TRIM_USERNAME"

6. 资源配置 (config/resource)

config/resource 文件声明应用的扩展能力,JSON 格式,必需。

6.1 数据共享 (data-share)

创建共享目录,用户通过 文件管理 → 应用文件 可访问,应用也可实时访问。

{
  "data-share": {
    "shares": [
      {
        "name": "documents",
        "permission": {
          "rw": ["myapp_user"]
        }
      },
      {
        "name": "documents/backups",
        "permission": {
          "ro": ["myapp_user"]
        }
      }
    ]
  }
}
字段 说明
name 共享目录名,支持多级(如 documents/backups)
permission.rw 读写权限用户列表
permission.ro 只读权限用户列表

6.2 系统集成 (usr-local-linker)

应用启动时自动创建软链接到系统目录,停止时自动移除。

{
  "usr-local-linker": {
    "bin": [
      "bin/myapp-cli",
      "bin/myapp-server"
    ],
    "lib": [
      "lib/mylib.so",
      "lib/mylib.a"
    ],
    "etc": [
      "etc/myapp.conf",
      "etc/myapp.d/default.conf"
    ]
  }
}
链接 目标目录
bin /usr/local/bin/
lib /usr/local/lib/
etc /usr/local/etc/

6.3 Docker 项目支持 (docker-project)

Docker 应用需要在 config/resource 中声明:

{
  "docker-project": {
    "projects": [
      {
        "name": "myapp-stack",
        "path": "docker"
      }
    ]
  }
}
字段 说明
name Docker Compose 项目名称
path 相对于 app 目录的路径,指向含 docker-compose.yaml 的文件夹

7. 入口配置 (app/ui/config)

应用入口是用户访问应用的"大门"。配置文件位于 app/ui/config(JSON 格式),入口键名必须以 appname 为前缀。

7.1 桌面图标入口

{
  ".url": {
    "myapp.main": {
      "title": "我的应用",
      "icon": "images/icon_{0}.png",
      "type": "url",
      "protocol": "http",
      "port": "8080",
      "url": "/",
      "allUsers": true
    },
    "myapp.admin": {
      "title": "管理后台",
      "icon": "images/admin_icon_{0}.png",
      "type": "url",
      "protocol": "http",
      "port": "8080",
      "url": "/admin",
      "allUsers": false
    }
  }
}

7.2 文件右键入口

{
  ".url": {
    "myapp.editor": {
      "title": "文本编辑器",
      "icon": "images/editor-{0}.png",
      "type": "url",
      "protocol": "http",
      "port": "8080",
      "url": "/edit",
      "allUsers": true,
      "fileTypes": ["txt", "md", "json", "xml"],
      "noDisplay": true
    }
  }
}

fileTypes + noDisplay: true 实现"只在文件右键菜单中显示,不在桌面显示"。

7.3 CGI 入口(推荐用于静态页面/Native 应用)

{
  ".url": {
    "myapp.Application": {
      "title": "我的应用",
      "icon": "images/icon_{0}.png",
      "type": "iframe",
      "protocol": "http",
      "url": "/cgi/ThirdParty/myapp/index.cgi/",
      "allUsers": true
    }
  }
}

CGI 方案不需要声明 port 字段。

7.4 入口字段参考

字段 必填 说明
title ✅ 入口显示标题(桌面图标名称/右键菜单名称)
icon ✅ 图标路径(相对 UI 目录),{0} 会被替换为 64 或 256
type ✅ url = 新标签页打开,iframe = 桌面窗口内嵌
protocol ✅ http / https / ""(空字符串=自适应协议;不声明则默认 http)
port ✅ 端口号;CGI 方案无需声明;可使用 ${wizard_port} 动态配置 (V1.1.8+)
url ✅ 访问路径(相对路径);可使用 ${wizard_url} 动态配置 (V1.1.8+)
allUsers ✅ true = 所有用户可见,false = 仅管理员可见
fileTypes ❌ 文件右键关联的文件扩展名数组,如 ["txt","md"]
noDisplay ❌ true = 不在桌面显示,仅右键菜单显示
accessPerm ❌ 桌面访问设置权限:editable / readonly / hidden
gatewaySocket ❌ 统一网关 Socket 文件名
gatewayPrefix ❌ 统一网关访问前缀,如 /app/myapp

入口配置文件支持环境变量 (V1.1.8+)

{
  "myapp.configurable": {
    "title": "可配置应用",
    "port": "${wizard_port}",
    "url": "${wizard_path}"
  }
}

控制字段

{
  "myapp.advanced": {
    "title": "高级功能",
    "control": {
      "accessPerm": "readonly"
    }
  }
}

7.5 文件路径参数

通过右键菜单打开文件时,系统自动在 URL 后拼接 path 参数:

http://localhost:8080/edit?path=/vol1/Users/admin/Documents/example.txt

7.6 CGI 脚本示例

app/ui/index.cgi — 通过 Shell 脚本实现静态资源转发:

#!/bin/bash

# 【注意】修改为你的静态文件根目录
BASE_PATH="/var/apps/App.Native.HelloFnosAppCenter/target/www"

# 从 REQUEST_URI 里拿到 index.cgi 后面的路径
URI_NO_QUERY="${REQUEST_URI%%?*}"
REL_PATH="/"

case "$URI_NO_QUERY" in
  *index.cgi*)
    REL_PATH="${URI_NO_QUERY#*index.cgi}"
    ;;
esac

if [ -z "$REL_PATH" ] || [ "$REL_PATH" = "/" ]; then
  REL_PATH="/index.html"
fi

TARGET_FILE="${BASE_PATH}${REL_PATH}"

# 防御 .. 越级访问
if echo "$TARGET_FILE" | grep -q '..'; then
  echo "Status: 400 Bad Request"
  echo "Content-Type: text/plain; charset=utf-8"
  echo ""
  echo "Bad Request"
  exit 0
fi

if [ ! -f "$TARGET_FILE" ]; then
  echo "Status: 404 Not Found"
  echo "Content-Type: text/plain; charset=utf-8"
  echo ""
  echo "404 Not Found: ${REL_PATH}"
  exit 0
fi

# 根据扩展名判断 Content-Type(精简版,完整版见社区参考)
ext="${TARGET_FILE##*.}"
case "$ext" in
  html|htm)  mime="text/html; charset=utf-8" ;;
  css)       mime="text/css; charset=utf-8" ;;
  js)        mime="application/javascript; charset=utf-8" ;;
  png)       mime="image/png" ;;
  jpg|jpeg)  mime="image/jpeg" ;;
  *)         mime="application/octet-stream" ;;
esac

echo "Content-Type: $mime"
echo ""
cat "$TARGET_FILE"

更多 CGI 实现参考:https://github.com/FNOSP/fnosAppCenterCgiCollection

8. 生命周期脚本 (cmd/)

系统通过调用 cmd/ 目录下的脚本来管理应用的全生命周期。所有脚本均为 Bash 脚本,全部必需(即使只写 exit 0)。

注意:不要用 echo 直接输出错误信息,而是写入 $TRIM_TEMP_LOGFILE;不要直接 exit,而是返回错误码 1。

8.1 cmd/main — 启动/停止/状态检查

#!/bin/bash

case $1 in
start)
  # 启动应用的命令,成功返回 0,失败返回 1
  exit 0
  ;;
stop)
  # 停止应用的命令,成功返回 0,失败返回 1
  exit 0
  ;;
status)
  # 检查应用运行状态,运行中返回 0,未运行返回 3
  exit 0
  ;;
*)
  exit 1
  ;;
esac

应用状态监控

返回值 含义
exit 0 应用正在运行
exit 3 应用未运行
exit 1 执行失败

系统会在启动前检查一次,运行期间定期轮询检查。

8.2 Native 应用完整启动脚本

#!/bin/bash

LOG_FILE="${TRIM_PKGVAR}/info.log"
PID_FILE="${TRIM_PKGVAR}/app.pid"
DATA_DIR="${TRIM_DATA_SHARE_PATHS%%:*}"
CMD="DATA_DIR=${DATA_DIR} PORT=5001 node ${TRIM_APPDEST}/server/server.js"

log_msg() {
  echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> ${LOG_FILE}
}

start_process() {
  if status; then return 0; fi
  log_msg "Starting process ..."
  bash -c "${CMD}" >> ${LOG_FILE} 2>&1 &
  printf "%s" "$!" > ${PID_FILE}
  return 0
}

stop_process() {
  log_msg "Stopping process ..."
  if [ -r "${PID_FILE}" ]; then
    pid=$(head -n 1 "${PID_FILE}" | tr -d '[:space:]')
    log_msg "pid=${pid}"
    if ! kill -0 "${pid}" 2>/dev/null; then
      rm -f "${PID_FILE}"
      return
    fi
    log_msg "send TERM signal to PID:${pid}..."
    kill -TERM ${pid} >> ${LOG_FILE} 2>&1
    local count=0
    while kill -0 "${pid}" 2>/dev/null && [ $count -lt 10 ]; do
      sleep 1; count=$((count + 1))
    done
    if kill -0 "${pid}" 2>/dev/null; then
      kill -KILL "${pid}"
    fi
    rm -f "${PID_FILE}"
  fi
}

status() {
  if [ -f "${PID_FILE}" ]; then
    pid=$(head -n 1 "${PID_FILE}" | tr -d '[:space:]')
    if kill -0 "${pid}" 2>/dev/null; then return 0; fi
    rm -f "${PID_FILE}"
  fi
  return 1
}

case $1 in
  start)  start_process ;;
  stop)   stop_process ;;
  status) if status; then exit 0; else exit 3; fi ;;
  *)      exit 1 ;;
esac

8.3 运行环境配置

Node.js

# 在 cmd 脚本中配置,可选版本:nodejs_v22 / v20 / v18 / v16 / v14
export PATH=/var/apps/nodejs_v22/target/bin:$PATH
node -v
npm -v

Python

# 可选版本:python312 / 311 / 310 / 39 / 38
export PATH=/var/apps/python312/target/bin:$PATH
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Java

# 可选版本:java-21-openjdk / java-17-openjdk / java-11-openjdk
export PATH=/var/apps/java-21-openjdk/target/bin:$PATH
java --version

8.4 应用配置与文件写入

飞牛 NAS 应用安装后,系统会在多个目录下为应用分配存储空间。不同目录的用途和权限差异很大,理解它们对写出健壮的应用至关重要。

目录一览

目录 权限 用途 系统写入 应用写入
/vol1/@appconf/{appname}/settings.conf 应用用户 应用配置(端口、路径等) ✅(向导/设置页面) ✅
/vol1/@appdata/{appname}/ 应用用户 应用数据(运行时日志、数据库等) ✘ ✅
/vol1/@apphome/{appname}/ 应用用户 应用家目录 ✘ ✅
/vol1/@apptemp/{appname}/ 应用用户 缓存/临时文件 ✘ ✅
/vol1/@appshare/{appname}/ 应用用户 共享目录(同 TRIM_DATA_SHARE_PATHS) ✘ ✅
/vol1/@appcenter/{appname}/ 应用用户 应用本身代码(只读) ✘(可手动修改但不建议) ✘

存储在 /vol1/@appcenter/ 下的是应用包的运行目录(app/server/ 等),不是 @appconf/。

settings.conf:应用配置的核心

/vol1/@appconf/{appname}/settings.conf 是 NAS 系统与应用之间的配置桥梁。它的格式为 KEY=VALUE 纯文本,系统会在以下时机自动重写此文件:

  1. 安装/更新时——写入 wizard/ 向导收集的用户输入(以 wizard_ 为前缀的环境变量)
  2. 应用设置页面修改时——写入授权文件夹、端口等系统级配置
  3. 应用本身也可以主动写入此文件,但要注意系统下次重写时会覆盖

⚠️ 常见陷阱:系统重写覆盖问题

# settings.conf 被系统重写后:
MUSIC_PATH=/vol1/1000/Music       ← 路径被系统改写,可能与真实路径名不符
PORT=5200

已知问题:

  1. 路径命名差异——用户实际选的文件夹名(如 music11 / music22)被系统写成了 Music(大小写不一致或截断)
  2. 授权文件夹丢失——用户授权了多个数据目录,但系统只保存了第一个
  3. 每次修改设置都会覆盖——手动改 settings.conf 后,只要用户进一次应用设置页面就会被重置
  4. data-share 可能为空——config/resource 中 data-share.shares 数组不更新,导致 TRIM_DATA_ACCESSIBLE_PATHS 环境变量为空

应对策略

策略一:应用启动时自读 settings.conf(推荐,最通用)

不依赖 cmd/main 传环境变量,让应用服务进程启动时自己读取 settings.conf。这样不管 NAS 系统以何种方式拉起进程(自动启动、手动重启)都能正确拿到配置。

// Node.js — 在读取 PORT / MUSIC_PATH 之前执行
var path = require('path');
var fs = require('fs');

// settings.conf 实际位置:
//   @appconf 的挂载点 = __dirname 减去 @appcenter 后的相对路径
var confPath = path.resolve(__dirname, '../../../@appconf/YOUR_APP_NAME/settings.conf');
try {
  var confContent = fs.readFileSync(confPath, 'utf-8');
  var confLines = confContent.split(String.fromCharCode(10));
  for (var i = 0; i < confLines.length; i++) {
    var line = confLines[i].trim();
    if (line && !line.startsWith('#')) {
      var eqIdx = line.indexOf('=');
      if (eqIdx > 0) {
        var key = line.substring(0, eqIdx).trim();
        var val = line.substring(eqIdx + 1).trim();
        if (process.env[key] === undefined) process.env[key] = val;
      }
    }
  }
} catch(e) { /* 配置文件不存在时忽略 */ }

const PORT = process.env.PORT || 6688;      // 已被 settings.conf 注入

注意:这段代码必须放在 PORT 声明之前,否则环境变量已经读过了,兜底逻辑不会生效。

# Python 版本
import os

def load_settings():
    config_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
                               '..', '@appconf', 'YOUR_APP_NAME', 'settings.conf')
    # 更准确的:从 __file__ 向上走到 @appcenter,再到 @appconf
    config_path = os.path.normpath(config_path)
    if not os.path.exists(config_path):
        return
    with open(config_path, 'r') as f:
        for line in f:
            line = line.strip()
            if not line or line.startswith('#'):
                continue
            if '=' in line:
                key, val = line.split('=', 1)
                key = key.strip()
                val = val.strip()
                if key not in os.environ:
                    os.environ[key] = val

load_settings()
PORT = int(os.environ.get('PORT', 6688))

策略二:只用 cmd/main 传环境变量(较脆弱)

# cmd/main 中正确的做法:读 settings.conf + 传环境变量
. /vol1/@appconf/YOUR_APP_NAME/settings.conf
export PORT MUSIC_PATH
node /vol1/@appcenter/YOUR_APP_NAME/server/server.js

局限:NAS 自动启动时可能不经过 cmd/main,环境变量仍然丢失。

策略三:验证路径有效性,失败时友好报错

var musicPaths = (process.env.MUSIC_PATH || '').split(':').filter(Boolean);
var validPaths = [];
musicPaths.forEach(function(p) {
  try {
    if (fs.statSync(p).isDirectory()) {
      validPaths.push(p);
    }
  } catch(e) {
    console.error('Music directory not found:', p);
  }
});
if (validPaths.length === 0) {
  console.error('No valid music folder. Please set it in App Settings.');
  process.exit(1);
}

实践建议

  1. 首选策略一(应用自读 settings.conf)+ 策略三(路径验证),覆盖所有启动场景
  2. 不要依赖 TRIM_DATA_ACCESSIBLE_PATHS 环境变量——它可能为空
  3. 对于 Native 应用,建议在 cmd/main 和 app 代码中都实现配置读取,双重保险
  4. 如果发现路径不匹配(如实际文件夹 music11 但配置写 Music),先确认是否是 NAS 系统写入的问题,不要在代码里做硬编码的路径猜测

⚠️ 端口变更的实战陷阱

问题描述

用户在安装向导/应用设置中修改端口(wizard_port)后,应用重启仍然监听默认端口。

根因分析

飞牛系统通过 TRIM_SERVICE_PORT 环境变量(来源:manifest 的 service_port 字段)将端口注入 cmd/main。但关键问题在于:

  • service_port 只在打包时固定,用户在安装向导或应用设置中修改端口后,service_port 不会自动更新
  • TRIM_SERVICE_PORT 的值在首次安装后即固定,不会因 wizard_port 变更而重新注入
  • config_callback 虽然能通过 $wizard_port 变量拿到新端口,但直接调用 cmd/main start 时传给 Node 的仍然是旧的 TRIM_SERVICE_PORT
修复方案:三级端口优先级

cmd/main 中端口获取应按以下优先级:

# 第一优先级:settings.conf 中持久化的 PORT(来自安装向导 / 应用设置的 wizard_port)
if [ -f "${TRIM_PKGETC}/settings.conf" ]; then
  . "${TRIM_PKGETC}/settings.conf"
fi
# 第二优先级:TRIM_SERVICE_PORT(系统注入,来自 manifest service_port)
# 第三优先级:默认值
PORT="${PORT:-${TRIM_SERVICE_PORT:-8080}}"
完整工作流
  1. 安装时:install_callback 将 $wizard_port 写入 ${TRIM_PKGETC}/settings.conf
  2. 修改端口时(应用设置页面):config_callback 将 $wizard_port 写入 settings.conf,然后重启
  3. 任何后续启动(系统自动启动、手动重启):cmd/main 优先读取 settings.conf 中的 PORT,不受 TRIM_SERVICE_PORT 旧值影响
配套的 config_callback
PORT="${wizard_port:-8080}"

# 先持久化端口到 settings.conf
mkdir -p "${TRIM_PKGETC}"
cat > "${TRIM_PKGETC}/settings.conf" <
要点
  • settings.conf 的自定义写入要放到 install_callback 和 config_callback 中,不要在 install_init 或其他脚本中写,否则可能在安装流程中被系统覆盖
  • TRIM_SERVICE_PORT 仍然可以作为 fallback 使用,但不要把它作为唯一的端口来源
  • 如果 settings.conf 被系统重写覆盖了 PORT(某些系统版本已知问题),检查端口是否仍然正确,必要时在 config_init 中添加校验

8.4 Docker 应用的 main 脚本

Docker 应用的启停由系统通过 compose 管理,但需要定义状态检查:

#!/bin/bash

FILE_PATH="${TRIM_APPDEST}/docker/docker-compose.yaml"

is_docker_running () {
  DOCKER_NAME=""
  if [ -f "$FILE_PATH" ]; then
    DOCKER_NAME=$(cat $FILE_PATH | grep "container_name" | awk -F ':' '{print $2}' | xargs)
    echo "DOCKER_NAME is set to: $DOCKER_NAME"
  fi
  if [ -n "$DOCKER_NAME" ]; then
    docker inspect $DOCKER_NAME | grep -q ""Status": "running"," || exit 1
    return
  fi
}

case $1 in
  start)  exit 0 ;;  # compose 管理,无需额外操作
  stop)   exit 0 ;;  # compose 管理,无需额外操作
  status) if is_docker_running; then exit 0; else exit 3; fi ;;
  *)      exit 1 ;;
esac

8.5 错误异常展示处理(V1.1.8+)

向 $TRIM_TEMP_LOGFILE 写入错误信息并返回错误码 1,系统会自动以 Dialog 对话框展示给用户:

# ✅ 正确做法
echo "配置文件不存在,应用启动失败!" > "${TRIM_TEMP_LOGFILE}"
exit 1

# ❌ 错误做法
# echo "配置文件不存在,应用启动失败!"
# exit 1

8.6 生命周期事件说明

事件 脚本 时机
安装前 install_init 文件解压前,可做环境检查、依赖安装
安装后 install_callback 文件解压后,可做初始化配置
卸载前 uninstall_init 停止应用后,卸载前
卸载后 uninstall_callback 可清理剩余数据和目录
升级前 upgrade_init 更新处理前
升级后 upgrade_callback 可做数据库升级、配置迁移
配置变更前 config_init 用户保存配置后,重启前
配置变更后 config_callback 可监听配置变化调整运行逻辑

卸载时保留/删除数据

系统默认保留 var 和 shares 目录。如果希望在卸载向导中让用户选择是否删除数据,在 wizard/uninstall 中配置选项,在 cmd/uninstall_callback 中根据用户选择清理:

if [ "$wizard_data_action" = "delete" ]; then
  rm -rf "${TRIM_PKGVAR}" "${TRIM_DATA_SHARE_PATHS}"
fi

9. 环境变量参考

9.1 系统环境变量

变量 说明
$TRIM_APPNAME 应用名称
$TRIM_APPVER 应用版本号
$TRIM_APPDEST 应用可执行文件目录(target)
$TRIM_PKGETC 配置文件目录(etc)
$TRIM_PKGVAR 运行时数据目录(var)
$TRIM_TEMP_LOGFILE 临时日志文件路径(用户可见)
$TRIM_SERVICE_PORT 服务端口
$TRIM_USERNAME 应用专用用户名
$TRIM_RUN_USERNAME 当前运行用户(root 或应用用户)
$TRIM_DATA_SHARE_PATHS 数据共享目录路径列表(冒号分隔)

9.2 向导输入变量

用户在向导中的选择会变成同名的环境变量,在脚本中可直接使用:

ADMIN_USERNAME="$wizard_admin_username"
DATABASE_TYPE="$wizard_database_type"
APP_PORT="$wizard_app_port"

9.3 应用入口中的动态变量(V1.1.8+)

入口配置文件 app/ui/config 中可使用 ${variable_name} 动态引用向导参数:

{
  "port": "${wizard_port}",
  "url": "${wizard_path}"
}

10. 向导配置 (wizard/)

向导是用户与应用交互的"引导员",JSON 格式,每个文件是一个 JSON 数组(多个步骤)。

10.1 向导类型

文件 用途
wizard/install 安装时的配置界面
wizard/uninstall 卸载时的确认界面
wizard/upgrade 更新时的配置界面
wizard/config 设置时的配置界面

10.2 向导文件结构

[
  {
    "stepTitle": "第一步标题",
    "items": [
      { 表单项 1 },
      { 表单项 2 }
    ]
  },
  {
    "stepTitle": "第二步标题",
    "items": [
      { 表单项 3 }
    ]
  }
]

10.3 表单项类型

text — 文本输入

{
  "type": "text",
  "field": "wizard_username",
  "label": "用户名",
  "initValue": "admin",
  "rules": [
    { "required": true, "message": "请输入用户名" },
    { "min": 3, "max": 20, "message": "长度应在3-20字符之间" }
  ]
}

password — 密码输入

{
  "type": "password",
  "field": "wizard_password",
  "label": "管理员密码",
  "rules": [
    { "required": true, "message": "请输入密码" },
    { "min": 6, "message": "密码长度不能少于6位" }
  ]
}

radio — 单选

{
  "type": "radio",
  "field": "wizard_install_type",
  "label": "安装类型",
  "initValue": "standard",
  "options": [
    { "label": "标准安装", "value": "standard" },
    { "label": "自定义安装", "value": "custom" }
  ],
  "rules": [{ "required": true, "message": "请选择安装类型" }]
}

checkbox — 多选

{
  "type": "checkbox",
  "field": "wizard_modules",
  "label": "安装模块",
  "initValue": ["web", "api"],
  "options": [
    { "label": "Web界面", "value": "web" },
    { "label": "API接口", "value": "api" },
    { "label": "数据库", "value": "database" }
  ],
  "rules": [{ "required": true, "message": "请至少选择一个模块" }]
}

select — 下拉选择

{
  "type": "select",
  "field": "wizard_database_type",
  "label": "数据库类型",
  "initValue": "sqlite",
  "options": [
    { "label": "SQLite (推荐)", "value": "sqlite" },
    { "label": "MySQL", "value": "mysql" },
    { "label": "PostgreSQL", "value": "postgresql" }
  ],
  "rules": [{ "required": true, "message": "请选择数据库类型" }]
}

switch — 开关

{
  "type": "switch",
  "field": "wizard_enable_backup",
  "label": "启用自动备份",
  "initValue": "true"
}

tips — 提示文本(不收集输入)

{
  "type": "tips",
  "helpText": "请阅读 隐私政策。"
}

10.4 验证规则

规则 示例
必填 {"required": true, "message": "此字段不能为空"}
最小长度 {"min": 3, "message": "长度不能少于3"}
最大长度 {"max": 50, "message": "长度不能超过50"}
精确长度 {"len": 6, "message": "请输入6位验证码"}
正则 {"pattern": "^[a-zA-Z0-9_]+$", "message": "只能含字母数字下划线"}

10.5 安装向导完整示例

[
  {
    "stepTitle": "欢迎安装",
    "items": [
      {
        "type": "tips",
        "helpText": "欢迎使用我们的应用!在开始使用前,请阅读并同意我们的服务条款。"
      },
      {
        "type": "switch",
        "field": "wizard_agree_terms",
        "label": "我已阅读并同意服务条款",
        "rules": [
          { "required": true, "message": "请同意服务条款" }
        ]
      }
    ]
  },
  {
    "stepTitle": "创建管理员账号",
    "items": [
      {
        "type": "text",
        "field": "wizard_admin_username",
        "label": "管理员用户名",
        "initValue": "admin",
        "rules": [
          { "required": true, "message": "请输入管理员用户名" },
          { "pattern": "^[a-zA-Z0-9_]+$", "message": "只能包含字母、数字和下划线" }
        ]
      },
      {
        "type": "password",
        "field": "wizard_admin_password",
        "label": "管理员密码",
        "rules": [
          { "required": true, "message": "请输入密码" },
          { "min": 8, "message": "密码长度不能少于8位" }
        ]
      },
      {
        "type": "password",
        "field": "wizard_admin_password_confirm",
        "label": "确认密码",
        "rules": [
          { "required": true, "message": "请确认密码" }
        ]
      }
    ]
  },
  {
    "stepTitle": "应用配置",
    "items": [
      {
        "type": "select",					
目录

热门AI工具

更多
DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

UP简历
UP简历 Hot

一款AI办公效率工具,主要用于基于AI技术的免费在线简历制作工具,适合需要提升相关任务效率的用户。

讯飞智作

讯飞智作是一款AI视频创作工具,AI文本配音工具,数字人课程、营销视频制作。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

AionClaw
AionClaw Hot

AionClaw是一款面向办公、创作和编程任务的AI桌面智能体。

相关专题

更多
Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

0

2026.10.28

Vite 如何搭配 Vue.js
Vite 如何搭配 Vue.js

Vite搭配Vue.js专题汇集创建Vue 3项目、配置开发环境、路径别名、Vue插件、代理、多环境构建与调试的已审核文章,帮助读者完成从初始化到日常开发的配置。

0

2026.10.28

Vite 项目目录怎么看
Vite 项目目录怎么看

Vite项目目录专题汇集入口文件、src源码、public静态资源、路径别名、项目根目录和构建输出目录相关的已审核文章,帮助读者理解各文件夹的用途与引用方式。

0

2026.10.28

Gradle插件开发入门教程
Gradle插件开发入门教程

本专题介绍Gradle插件开发的基本流程,涵盖插件类编写、自定义任务注册、扩展参数设计和插件应用方法,帮助你理解插件如何组织构建逻辑,减少多个项目之间的重复配置。

0

2026.10.22

Gradle Wrapper配置教程
Gradle Wrapper配置教程

本专题介绍Gradle Wrapper的基本作用,涵盖文件组成、生成方法、版本配置和任务调用,帮助你理解项目如何使用指定的Gradle版本,减少不同开发环境带来的构建差异。

0

2026.10.22

Golangci-lint检查器使用教程
Golangci-lint检查器使用教程

本专题整理Golangci-lint常用Linters的用途与配置方法,覆盖错误处理、无效代码、安全、复杂度、命名和依赖检查,帮助项目合理选规则。

0

2026.10.22

Gradle怎么构建Java项目
Gradle怎么构建Java项目

本专题介绍Gradle Java项目构建方法,涵盖Java插件配置、依赖声明、源码编译、测试执行和打包检查,帮助你理解各阶段的任务关系,逐步完成项目构建。

0

2026.10.22

PHP时间戳漏洞修复教程
PHP时间戳漏洞修复教程

本专题汇总PHP常见时间戳漏洞,包括2038年溢出、时区漂移、时间戳伪造、重放攻击、时间盲注与日志审计失效等,并给出升级64位环境、使用 DateTimeImmutable、统一UTC、严格超时校验、预处理绑定、hash_equals 比对等修复方案,帮助开发者快速排查和加固时间相关代码。

0

2026.10.22

Golangci-lint配置文件与规则设置
Golangci-lint配置文件与规则设置

本专题聚焦Golangci-lint YAML配置文件,讲清运行参数、规则启停、目录排除、问题过滤及严重级别设置,便于团队统一检查标准。

0

2026.10.22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn