{"id":"f4f79a3f-4f41-4b14-9884-3e9974aad00c","entityType":"agent","slug":"clawhub-dalingo81-fn-fpk","name":"fn-fpk","canonicalUrl":"https://www.xpersona.co/agent/clawhub-dalingo81-fn-fpk","canonicalPath":"/agent/clawhub-dalingo81-fn-fpk","generatedAt":"2026-10-11T03:55:51.691Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-11T01:40:48.672Z","emptyReason":null},"description":"飞牛NAS (fnOS) FPK 应用打包开发技能。使用此技能开发和打包飞牛NAS第三方应用（.fpk），包括：Native 应用（Node.js/Python/Java/Go/Shell 等）和 Docker 应用。涵盖整个开发周期：开发环境准备、fnpack 创建项目、manifest 配置、权限/资源配置、用户入口配置（应用入口 app/ui/config + 桌面图标 + 文件右键菜单）、生命周期脚本编写（cmd/main）、向导配置（wizard）、图标规范、CGI 反向代理、统一网关注册/认证、运行时环境（Python/Node.js/Java）、中间件服务（Redis/MinIO/RabbitMQ/MariaDB）、依赖管理、fnpack CLI 打包、appcenter-cli 测试安装、到上架发布；以及飞牛开放平台 Open API 接入（文件授权、页面路由、界面语言/主题、后端平台配置查询、前端 JS SDK @trimjs/web-app）。用户提到\"飞牛\"、\"fnOS\"、\"FPK\"、\"飞牛应用\"、\"开放 API\"、\"Open API\"、\"@trimjs/web-app\"、\"文件授权\"、\"授权路径\"、\"trim.file\"时触发。 Skill: fn-fpk Owner: dalingo81 Summary: 飞牛NAS (fnOS) FPK 应用打包开发技能。使用此技能开发和打包飞牛NAS第三方应用（.fpk），包括：Native 应用（Node.js/Python/Java/Go/Shell 等）和 Docker 应用。涵盖整个开发周期：开发环境准备、fnpack 创建项目、manifest 配置、权限/资源配置、用户入口配置（应用入口 app/ui/config + 桌面图标 + 文件右键菜单）、生命周期脚本编写（cmd/main）、向导配置（wizard）、图标规范、CGI 反向代理、统一网关注册/认证、运行时环境（Python/Node.js/Java）、中间件服务（Redis/MinIO/RabbitMQ/MariaDB）、依赖管理、fnpack CLI 打包、appcenter-cli 测试安装、到上架发布；以及飞牛开放平台 Open API","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1.2K downloads reported by the source. Last updated 10/11/2026.","installCommand":"clawhub skill install s17fm5y7gm95aa1r5m04hw8tbs87dqj6:fn-fpk","sourceUrl":"https://clawhub.ai/dalingo81/fn-fpk","homepage":"https://clawhub.ai/dalingo81/skills/fn-fpk","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/dalingo81/fn-fpk","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/dalingo81/skills/fn-fpk","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":62,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"飞牛NAS (fnOS) FPK 应用打包开发技能。使用此技能开发和打包飞牛NAS第三方应用（.fpk），包括：Native 应用（Node.js/Python/Java/Go/Shell 等）和 Docker 应用。涵盖整个开发周期：开发环境准备、fnpack 创建项目、manifest 配置、权限/资源配置、用户入"},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-11T01:40:48.672Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T01:40:48.672Z","emptyReason":null},"stars":null,"forks":null,"downloads":1202,"packageName":null,"latestVersion":"1.3.1","tractionLabel":"1.2K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T01:40:48.604Z","emptyReason":null},"lastUpdatedAt":"2026-10-11T01:40:48.672Z","lastCrawledAt":"2026-10-11T01:40:48.604Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-12T01:40:48.604Z","lastVerifiedAt":null,"highlights":[{"version":"1.3.1","createdAt":"2026-08-28T06:55:27.952Z","changelog":"新增飞牛开放平台 Open API 整章（文件授权/页面路由/界面语言主题/后端平台配置/前端 JS SDK @trimjs/web-app）；fnpack 升级至 1.2.3；修正架构支持 ARM64","fileCount":4,"zipByteSize":26650},{"version":"1.2.2","createdAt":"2026-06-04T15:30:31.586Z","changelog":"2026-06-04: 基于官方文档逐页复审，补充环境变量完整示例、manifest等号对齐格式参考、control.accessPerm字段说明、CGI集合仓库链接","fileCount":3,"zipByteSize":21176},{"version":"1.3.0","createdAt":"2026-06-03T07:07:24.023Z","changelog":"v1.3.0 - 2026-06-02 基于官方文档全面更新 - **新增**: 开发环境准备（系统要求、技术栈、CLI 工具安装） - **新增**: manifest 字段详解（platform V1.1.8+、install_type、ctl_stop、changelog 等） - **新增**: 入口配置完整指南（桌面图标、文件右键菜单、CGI 入口、环境变量支持 V1.1.8+） - **新增**: CGI 脚本完整示例（含路径安全防御） - **新增**: 错误异常展示处理（$TRIM_TEMP_LOGFILE + Dialog） - **新增**: 运行时环境（Python/Node.js/Java 版本选择 + PATH 配置） - **新增**: 中间件服务（Redis/MinIO/RabbitMQ/MariaDB 连接示例） - **新增**: 应用依赖关系（顺序、嵌套依赖处理） - **新增**: 统一网关注册（gatewayPrefix + gatewaySocket）+ WebSocket 支持 - **新增**: 登录认证（X-Trim-* Header 透传、权限判断） - **新增**: Docker 应用构建详解（docker-compose 环境变量替换） - **新增**: Native 应用完整构建流程（从开发→打包→集成编译） - **新增**: 图标规范（ICON.PNG + app/ui/images/icon_64.png 双层体系） - **新增**: fnpack 打包校验规则表 - **更新**: appcenter-cli（install-fpk/install-local/manual-install/default-volume） - **更新**: fnpack 1.2.1（新增 Linux ARM、补全校验） - **新增**: 文档更新历史 - **新增**: 附录命令行速查","fileCount":3,"zipByteSize":20423},{"version":"1.2.0","createdAt":"2026-06-02T03:48:05.691Z","changelog":"Major update based on developer.fnnas.com official docs (2026-06-02). New sections: dev env setup, manifest field reference (platform V1.1.8+/install_type/ctl_stop/changelog), app entry guide (desktop icons/file context menu/CGI/env var support), CGI script with path security, error handling via TRIM_TEMP_LOGFILE+Dialog, runtime env (Python/Node.js/Java+PATH), middleware services (Redis/MinIO/RabbitMQ/MariaDB), app dependency (ordering/nested), unified gateway (gatewayPrefix+gatewaySocket)+WebSocket, auth (X-Trim-* headers), Docker app build (compose env subst), Native app full workflow, icon spec, fnpack validation table, changelog history, CLI cheat sheet.","fileCount":3,"zipByteSize":17021},{"version":"1.0.0","createdAt":"2026-05-25T17:48:22.541Z","changelog":"飞牛NAS FPK应用打包开发技能：支持Native和Docker应用的全生命周期开发","fileCount":3,"zipByteSize":22743}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s17fm5y7gm95aa1r5m04hw8tbs87dqj6:fn-fpk","setupComplexity":"medium","setupSteps":["Setup complexity is MEDIUM. Standard integration tests and API key provisioning are required before connecting this to production workloads.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-dalingo81-fn-fpk/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-dalingo81-fn-fpk/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-dalingo81-fn-fpk/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-dalingo81-fn-fpk/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-dalingo81-fn-fpk/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-dalingo81-fn-fpk/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-11T03:55:51.688Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-dalingo81-fn-fpk/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-dalingo81-fn-fpk/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-dalingo81-fn-fpk/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-dalingo81-fn-fpk/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"high","updatedAt":"2026-10-11T01:40:48.672Z","emptyReason":null},"readme":"Skill: fn-fpk\n\nOwner: dalingo81\n\nSummary: 飞牛NAS (fnOS) FPK 应用打包开发技能。使用此技能开发和打包飞牛NAS第三方应用（.fpk），包括：Native 应用（Node.js/Python/Java/Go/Shell 等）和 Docker 应用。涵盖整个开发周期：开发环境准备、fnpack 创建项目、manifest 配置、权限/资源配置、用户入口配置（应用入口 app/ui/config + 桌面图标 + 文件右键菜单）、生命周期脚本编写（cmd/main）、向导配置（wizard）、图标规范、CGI 反向代理、统一网关注册/认证、运行时环境（Python/Node.js/Java）、中间件服务（Redis/MinIO/RabbitMQ/MariaDB）、依赖管理、fnpack CLI 打包、appcenter-cli 测试安装、到上架发布；以及飞牛开放平台 Open API 接入（文件授权、页面路由、界面语言/主题、后端平台配置查询、前端 JS SDK @trimjs/web-app）。用户提到\"飞牛\"、\"fnOS\"、\"FPK\"、\"飞牛应用\"、\"开放 API\"、\"Open API\"、\"@trimjs/web-app\"、\"文件授权\"、\"授权路径\"、\"trim.file\"时触发。\n\nTags: app-dev:1.2.0, flynasa:1.2.0, fnos:1.2.0, fpks:1.2.0, latest:1.3.1, nas:1.2.0\n\nVersion history:\n\nv1.3.1 | 2026-08-28T06:55:27.952Z | user\n\n新增飞牛开放平台 Open API 整章（文件授权/页面路由/界面语言主题/后端平台配置/前端 JS SDK @trimjs/web-app）；fnpack 升级至 1.2.3；修正架构支持 ARM64\n\nv1.2.2 | 2026-06-04T15:30:31.586Z | user\n\n2026-06-04: 基于官方文档逐页复审，补充环境变量完整示例、manifest等号对齐格式参考、control.accessPerm字段说明、CGI集合仓库链接\n\nv1.3.0 | 2026-06-03T07:07:24.023Z | user\n\nv1.3.0 - 2026-06-02 基于官方文档全面更新\n\n- **新增**: 开发环境准备（系统要求、技术栈、CLI 工具安装）\n- **新增**: manifest 字段详解（platform V1.1.8+、install_type、ctl_stop、changelog 等）\n- **新增**: 入口配置完整指南（桌面图标、文件右键菜单、CGI 入口、环境变量支持 V1.1.8+）\n- **新增**: CGI 脚本完整示例（含路径安全防御）\n- **新增**: 错误异常展示处理（$TRIM_TEMP_LOGFILE + Dialog）\n- **新增**: 运行时环境（Python/Node.js/Java 版本选择 + PATH 配置）\n- **新增**: 中间件服务（Redis/MinIO/RabbitMQ/MariaDB 连接示例）\n- **新增**: 应用依赖关系（顺序、嵌套依赖处理）\n- **新增**: 统一网关注册（gatewayPrefix + gatewaySocket）+ WebSocket 支持\n- **新增**: 登录认证（X-Trim-* Header 透传、权限判断）\n- **新增**: Docker 应用构建详解（docker-compose 环境变量替换）\n- **新增**: Native 应用完整构建流程（从开发→打包→集成编译）\n- **新增**: 图标规范（ICON.PNG + app/ui/images/icon_64.png 双层体系）\n- **新增**: fnpack 打包校验规则表\n- **更新**: appcenter-cli（install-fpk/install-local/manual-install/default-volume）\n- **更新**: fnpack 1.2.1（新增 Linux ARM、补全校验）\n- **新增**: 文档更新历史\n- **新增**: 附录命令行速查\n\nv1.2.0 | 2026-06-02T03:48:05.691Z | user\n\nMajor update based on developer.fnnas.com official docs (2026-06-02). New sections: dev env setup, manifest field reference (platform V1.1.8+/install_type/ctl_stop/changelog), app entry guide (desktop icons/file context menu/CGI/env var support), CGI script with path security, error handling via TRIM_TEMP_LOGFILE+Dialog, runtime env (Python/Node.js/Java+PATH), middleware services (Redis/MinIO/RabbitMQ/MariaDB), app dependency (ordering/nested), unified gateway (gatewayPrefix+gatewaySocket)+WebSocket, auth (X-Trim-* headers), Docker app build (compose env subst), Native app full workflow, icon spec, fnpack validation table, changelog history, CLI cheat sheet.\n\nv1.0.0 | 2026-05-25T17:48:22.541Z | user\n\n飞牛NAS FPK应用打包开发技能：支持Native和Docker应用的全生命周期开发\n\nArchive index:\n\nArchive v1.3.1: 4 files, 26650 bytes\n\nFiles: _icon.png (1515b), skill-card.md (2418b), SKILL.md (64501b), _meta.json (125b)\n\nFile v1.3.1:SKILL.md\n\n---\nname: fn-fpk\ndescription: 飞牛NAS (fnOS) FPK 应用打包开发技能。使用此技能开发和打包飞牛NAS第三方应用（.fpk），包括：Native 应用（Node.js/Python/Java/Go/Shell 等）和 Docker 应用。涵盖整个开发周期：开发环境准备、fnpack 创建项目、manifest 配置、权限/资源配置、用户入口配置（应用入口 app/ui/config + 桌面图标 + 文件右键菜单）、生命周期脚本编写（cmd/main）、向导配置（wizard）、图标规范、CGI 反向代理、统一网关注册/认证、运行时环境（Python/Node.js/Java）、中间件服务（Redis/MinIO/RabbitMQ/MariaDB）、依赖管理、fnpack CLI 打包、appcenter-cli 测试安装、到上架发布；以及飞牛开放平台 Open API 接入（文件授权、页面路由、界面语言/主题、后端平台配置查询、前端 JS SDK @trimjs/web-app）。用户提到\"飞牛\"、\"fnOS\"、\"FPK\"、\"飞牛应用\"、\"开放 API\"、\"Open API\"、\"@trimjs/web-app\"、\"文件授权\"、\"授权路径\"、\"trim.file\"时触发。\n---\n\n# fn-fpk — 飞牛 NAS fnOS FPK 应用开发\n\n> 基于官方文档 https://developer.fnnas.com 于 2026-08-28 全面更新（新增开放平台 Open API 整章）。\n> 系统架构: x86_64 (AMD64) 与 ARM64 均支持；Linux 内核 6.12.18+, Debian 发行版。\n> fnpack 版本: 1.2.3, appcenter-cli 预装在 fnOS 中。\n\n---\n\n## 1. 开发环境准备\n\n### 1.1 系统要求\n- **fnOS 版本**: ≥ 0.9.27\n- **架构**: 支持 **x86_64 (AMD64)** 与 **ARM64**。manifest `platform` 字段可选 `x86` / `arm` / `all`（Docker 应用常用 `all`）；纯 Native 应用需按目标架构交叉编译。fnpack 同时提供 `linux-amd64` 与 `linux-arm64` 二进制\n- **存储**: 至少创建一个存储空间\n- **权限**: 管理员权限（安装/卸载/系统设置）\n\n### 1.2 开发方式\n- **本地开发**: 在本地用 fnpack CLI 打包，再传送到 fnOS 设备安装\n- **远程开发**: SSH 到 fnOS 设备直接开发，结合 `appcenter-cli install-local` 快速测试\n\n### 1.3 技术栈\n| 语言/框架 | 支持情况 |\n|-----------|---------|\n| Node.js | ✅ (v14/v16/v18/v20/v22) |\n| Python | ✅ (3.8/3.9/3.10/3.11/3.12) |\n| Java | ✅ (11/17/21 OpenJDK) |\n| Go | ✅ Linux 运行时支持 |\n| Shell | ✅ (Bash) |\n| HTML/JS/CSS | ✅ 前端任意框架 |\n\n### 1.4 CLI 工具\n\n#### fnpack 打包工具\n下载地址：https://static2.fnnas.com/fnpack/fnpack-1.2.3-{os}-{arch}\n\n| 平台 | 二进制 |\n|------|--------|\n| Windows x86 | `fnpack-1.2.3-windows-amd64` |\n| Linux x86 | `fnpack-1.2.3-linux-amd64` |\n| Linux ARM | `fnpack-1.2.3-linux-arm64` |\n| macOS Intel | `fnpack-1.2.3-darwin-amd64` |\n| macOS M 系列 | `fnpack-1.2.3-darwin-arm64` |\n\n安装：\n```bash\nchmod +x fnpack-1.2.3-linux-amd64\nsudo mv fnpack-1.2.3-linux-amd64 /usr/local/bin/fnpack\nfnpack --help\n```\n\n#### appcenter-cli （预装在 fnOS 中）\n```bash\n# 安装 fpk 文件\nappcenter-cli install-fpk myapp.fpk\n\n# 从本地目录安装（开发模式，无需打包）\ncd /path/to/myapp\nappcenter-cli install-local\n\n# 管理应用\nappcenter-cli list              # 已安装列表\nappcenter-cli start myapp       # 启动\nappcenter-cli stop myapp        # 停止\n\n# 手动安装功能（仅测试用途）\nappcenter-cli manual-install             # 查看状态\nappcenter-cli manual-install enable      # 开启\nappcenter-cli manual-install disable     # 关闭\n\n# 设置默认存储空间\nappcenter-cli default-volume             # 查看当前\nappcenter-cli default-volume 1           # 设置存储空间1\n\n# 静默安装（跳过向导）\nappcenter-cli install-fpk myapp.fpk --env config.env\n```\n\n---\n\n## 2. 应用类型\n\n| 类型 | 描述 | 创建命令 |\n|------|------|----------|\n| **Native 应用** | 直接运行在 fnOS 上的应用 | `fnpack create <appname>` |\n| **Docker 应用** | 基于 Docker Compose 容器编排 | `fnpack create <appname> --template docker` |\n| **纯服务应用**（无 UI） | 无 Web 访问入口 | 加 `--without-ui true` |\n\n---\n\n## 3. 项目结构与核心文件\n\n### 3.1 通用结构（fnpack create 生成）\n\n```\nmyapp/\n├── app/                      # 应用可执行文件/资源目录\n│   ├── server/               # 后台服务程序（Native 应用）\n│   ├── ui/                   # Web UI 入口配置\n│   │   ├── images/           # 入口图标（icon_64.png, icon_256.png）\n│   │   └── config            # 入口配置文件（JSON）\n│   ├── www/                  # Web 静态资源（HTML/CSS/JS）\n│   └── docker/               # Docker Compose 文件（Docker 应用）\n│       └── docker-compose.yaml\n├── manifest                  # 应用基本信息（必需）\n├── cmd/                      # 生命周期管理脚本（全部必需）\n│   ├── main                  # 启动/停止/状态检查\n│   ├── install_init          # 安装前初始化\n│   ├── install_callback      # 安装后回调\n│   ├── uninstall_init        # 卸载前\n│   ├── uninstall_callback    # 卸载后\n│   ├── upgrade_init          # 升级前\n│   ├── upgrade_callback      # 升级后\n│   ├── config_init           # 配置变更前\n│   └── config_callback       # 配置变更后\n├── config/\n│   ├── privilege             # 权限配置（JSON，必需）\n│   └── resource              # 资源配置（JSON，必需）\n├── wizard/                   # 向导配置（可选）\n│   ├── install               # 安装向导\n│   ├── uninstall             # 卸载向导\n│   ├── upgrade               # 更新向导\n│   └── config                # 配置向导\n├── ICON.PNG                  # 64×64 应用图标（必需）\n├── ICON_256.PNG              # 256×256 应用图标（必需）\n└── LICENSE                   # 许可证（可选）\n```\n\n### 3.2 安装后的目录结构\n\n当应用安装到 fnOS 后，系统创建如下目录：\n\n```\n/var/apps/[appname]/\n├── cmd/                      # 生命周期脚本（来自包）\n├── config/\n│   ├── privilege             # 权限配置\n│   └── resource              # 资源配置\n├── ICON_256.PNG\n├── ICON.PNG\n├── LICENSE\n├── manifest\n├── etc -> /vol{volume}/@appconf/[appname]     # 静态配置文件\n├── home -> /vol{volume}/@apphome/[appname]    # 用户数据\n├── target -> /vol{volume}/@appcenter/[appname] # 可执行文件\n├── tmp -> /vol{volume}/@apptemp/[appname]     # 临时文件\n├── var -> /vol{volume}/@appdata/[appname]     # 运行时数据\n├── shares/                   # 共享数据目录（按 resource 配置）\n│   ├── datashare1 -> /vol{volume}/@appshare/datashare1\n│   └── datashare2 -> /vol{volume}/@appshare/datashare2\n└── wizard/                   # 向导配置（安装/卸载/升级/配置）\n    ├── install\n    ├── uninstall\n    ├── upgrade\n    └── config\n```\n\n---\n\n## 4. manifest — 应用基本信息\n\n`manifest` 文件是应用的\"身份证\"，放在项目根目录，**无扩展名**。\n\n### 4.1 完整字段参考\n\n```\n# ═══════════════ 基本信息 ═══════════════\nappname        = myapp                        # ① 应用唯一标识，系统全局唯一\nversion        = 1.0.0                        # ② 版本号：x[.y[.z]][-build]\ndisplay_name   = 我的应用                      # ③ 用户可见的名称\ndesc           = 这是一个示例应用              # ④ 详细介绍，支持 HTML 格式\nsource         = thirdparty                   # ⑤ 固定值：thirdparty\n\n# ═══════════════ 系统要求 ═══════════════\nplatform       = x86                          # ⑥ 架构：x86 | arm | all (V1.1.8+)\narch           = x86_64                       # ⑦ 已废弃，请用 platform\nos_min_version = 0.9.0                        # ⑧ 最低系统版本\nos_max_version = 0.9.100                      # ⑨ 最高系统版本\n\n# ═══════════════ 开发者信息 ═══════════════\nmaintainer     = 张三                          # 开发者/团队名称\nmaintainer_url = https://example.com           # 开发者网站\ndistributor    = 示例公司                      # 发布者\ndistributor_url = https://company.com          # 发布者网站\n\n# ═══════════════ 安装与运行控制 ═══════════════\ninstall_type   =                              # 安装位置：空=用户可选存储空间，root=系统分区\nctl_stop       = true                         # 是否显示启动/停止按钮，默认 true\ncheckport      = true                         # 是否启用端口检查，默认 true\nservice_port   = 8080                         # 应用监听端口（单个端口）\ndisable_authorization_path = false            # 是否禁用授权目录功能\n\n# ═══════════════ 用户界面 ═══════════════\ndesktop_uidir          = ui                   # UI 组件目录（相对应用根目录）\ndesktop_applaunchname  = myapp.Application    # 默认启动入口 ID\nmicro_app              = true                 # 启用微应用环境；使用前端 JS SDK (@trimjs/web-app) 调用开放 API 时必须声明\n\n# ═══════════════ 依赖管理 ═══════════════\ninstall_dep_apps = mariaDB:redis              # 依赖应用列表，格式：app1>2.2.2:app2:app3\n\n# ═══════════════ 应用更新 ═══════════════\nchangelog = 新增了XX功能                       # 更新日志（升级时展示）\n```\n\n### 4.2 字段详解\n\n#### 应用标识\n| 字段 | 必填 | 说明 |\n|------|------|------|\n| `appname` | ✅ | 全局唯一标识符，用于系统识别 |\n| `version` | ✅ | 格式：`x[.y[.z]][-build]`，例如 `1.0.0`、`2.1.3-beta` |\n| `display_name` | ✅ | 应用中心显示的名称 |\n| `desc` | ✅ | 详细介绍，支持 HTML 格式 |\n| `source` | ✅ | 固定为 `thirdparty` |\n\n#### 系统要求（V1.1.8+ 新增 platform 字段）\n| 字段 | 说明 |\n|------|------|\n| `platform = x86` | 仅支持 x86 架构 |\n| `platform = arm` | 仅支持 arm 架构 |\n| `platform = all` | 所有架构，Docker 应用常用 |\n| `arch` | 【已废弃】用 platform 替代 |\n\n#### 安装控制\n| 字段 | 默认值 | 说明 |\n|------|--------|------|\n| `install_type` | 空 | `root` = 安装到系统分区 `/usr/local/apps/@appcenter/`；空 = 用户选择存储位置 |\n| `ctl_stop` | `true` | `false` 时隐藏启动/停止按钮和运行状态（无进程应用） |\n| `checkport` | `true` | `false` 时系统不检查端口占用 |\n| `service_port` | — | 应用监听端口（仅支持单个端口） |\n| `disable_authorization_path` | `false` | `true` 时应用设置页不显示授权目录操作 |\n\n#### 依赖管理\n```ini\n# 格式：app1>2.2.2:app2:app3\n# > 表示最低版本要求\n# : 分隔多个依赖\n# 系统按列表顺序自动安装依赖\ninstall_dep_apps = mariaDB:redis\n```\n\n---\n\n## 5. 权限配置 (config/privilege)\n\n`config/privilege` 文件定义应用运行时的权限级别和用户身份，JSON 格式，**必需**。\n\n### 5.1 默认权限模式（推荐）\n\n```json\n{\n  \"defaults\": {\n    \"run-as\": \"package\"\n  },\n  \"username\": \"myapp_user\",\n  \"groupname\": \"myapp_group\"\n}\n```\n\n| 字段 | 默认值 | 说明 |\n|------|--------|------|\n| `run-as` | `package` | `package` = 应用用户模式，`root` = root 模式 |\n| `username` | appname | 应用专用用户名 |\n| `groupname` | appname | 应用专用用户组名 |\n\n#### 默认模式行为\n- 系统为应用创建专用用户和用户组\n- 所有应用进程以专用用户身份运行\n- 应用文件所有者是该专用用户\n- 应用只能访问自己的目录和系统允许的公共资源\n\n### 5.2 Root 权限模式\n\n> ⚠️ 仅飞牛官方合作的企业开发者可使用，第三方应用默认无法在应用中心发布 root 权限应用。\n\n```json\n{\n  \"defaults\": {\n    \"run-as\": \"root\"\n  },\n  \"username\": \"myapp_user\",\n  \"groupname\": \"myapp_group\"\n}\n```\n\n#### Root 模式行为\n- 应用脚本以 root 身份执行\n- 应用进程可以 root 身份或指定应用用户身份运行\n- 应用文件所有者变为 root\n- 系统仍会创建应用专用用户和用户组\n\n### 5.3 外部文件访问权限\n\n应用默认无法访问用户个人文件。用户需要在应用设置中授权：\n- **读写权限**：读取和修改文件\n- **只读权限**：只能读取，不能修改\n- **禁止访问**：无法访问该路径\n\n也可通过 `config/resource` 的 `data-share` 设置默认共享目录。\n\n### 5.4 当前用户检查\n\n```bash\necho \"当前运行用户: $TRIM_RUN_USERNAME\"\necho \"应用专用用户: $TRIM_USERNAME\"\n```\n\n---\n\n## 6. 资源配置 (config/resource)\n\n`config/resource` 文件声明应用的扩展能力，JSON 格式，**必需**。\n\n### 6.0 开放 API Scope 声明 (api-scope)\n\n若应用要调用飞牛开放 API（文件授权、页面路由、平台配置等，见 §21），需在 `config/resource` 中声明用到的 Scope。**只声明确实会用到的**，不要写满。Scope 仅作为接入前提，实际可访问内容仍取决于用户授权、管理员设置与 token 校验。\n\n```json\n{\n  \"api-scope\": [\n    \"trim.file.userAccess\",\n    \"trim.file.userAcl\"\n  ]\n}\n```\n\n| Scope | 能力 |\n|-------|------|\n| `trim.file.sharedAccess` | 应用共享授权（管理员授权目录，查询/删除） |\n| `trim.file.userAccess` | 用户个人授权（用户选择/授权自己的目录或文件，查询/删除） |\n| `trim.file.userAcl` | 检查某用户对指定路径是否可读/写/删 |\n| `trim.file.path` | 内部路径 `/vol1/...` 转用户可读路径 |\n| `trim.system.getPlatformConfig` | 后端读取系统语言与系统版本 |\n\n> 前端 JS SDK 调用开放能力前，还需在 `manifest` 声明 `micro_app=true`（见 §4.1）。\n\n### 6.1 数据共享 (data-share)\n\n创建共享目录，用户通过 **文件管理 → 应用文件** 可访问，应用也可实时访问。\n\n```json\n{\n  \"data-share\": {\n    \"shares\": [\n      {\n        \"name\": \"documents\",\n        \"permission\": {\n          \"rw\": [\"myapp_user\"]\n        }\n      },\n      {\n        \"name\": \"documents/backups\",\n        \"permission\": {\n          \"ro\": [\"myapp_user\"]\n        }\n      }\n    ]\n  }\n}\n```\n\n| 字段 | 说明 |\n|------|------|\n| `name` | 共享目录名，支持多级（如 `documents/backups`） |\n| `permission.rw` | 读写权限用户列表 |\n| `permission.ro` | 只读权限用户列表 |\n\n### 6.2 系统集成 (usr-local-linker)\n\n应用启动时自动创建软链接到系统目录，停止时自动移除。\n\n```json\n{\n  \"usr-local-linker\": {\n    \"bin\": [\n      \"bin/myapp-cli\",\n      \"bin/myapp-server\"\n    ],\n    \"lib\": [\n      \"lib/mylib.so\",\n      \"lib/mylib.a\"\n    ],\n    \"etc\": [\n      \"etc/myapp.conf\",\n      \"etc/myapp.d/default.conf\"\n    ]\n  }\n}\n```\n\n| 链接 | 目标目录 |\n|------|----------|\n| `bin` | `/usr/local/bin/` |\n| `lib` | `/usr/local/lib/` |\n| `etc` | `/usr/local/etc/` |\n\n### 6.3 Docker 项目支持 (docker-project)\n\nDocker 应用需要在 `config/resource` 中声明：\n\n```json\n{\n  \"docker-project\": {\n    \"projects\": [\n      {\n        \"name\": \"myapp-stack\",\n        \"path\": \"docker\"\n      }\n    ]\n  }\n}\n```\n\n| 字段 | 说明 |\n|------|------|\n| `name` | Docker Compose 项目名称 |\n| `path` | 相对于 app 目录的路径，指向含 `docker-compose.yaml` 的文件夹 |\n\n---\n\n## 7. 入口配置 (app/ui/config)\n\n应用入口是用户访问应用的\"大门\"。配置文件位于 `app/ui/config`（JSON 格式），入口键名必须以 `appname` 为前缀。\n\n### 7.1 桌面图标入口\n\n```json\n{\n  \".url\": {\n    \"myapp.main\": {\n      \"title\": \"我的应用\",\n      \"icon\": \"images/icon_{0}.png\",\n      \"type\": \"url\",\n      \"protocol\": \"http\",\n      \"port\": \"8080\",\n      \"url\": \"/\",\n      \"allUsers\": true\n    },\n    \"myapp.admin\": {\n      \"title\": \"管理后台\",\n      \"icon\": \"images/admin_icon_{0}.png\",\n      \"type\": \"url\",\n      \"protocol\": \"http\",\n      \"port\": \"8080\",\n      \"url\": \"/admin\",\n      \"allUsers\": false\n    }\n  }\n}\n```\n\n### 7.2 文件右键入口\n\n```json\n{\n  \".url\": {\n    \"myapp.editor\": {\n      \"title\": \"文本编辑器\",\n      \"icon\": \"images/editor-{0}.png\",\n      \"type\": \"url\",\n      \"protocol\": \"http\",\n      \"port\": \"8080\",\n      \"url\": \"/edit\",\n      \"allUsers\": true,\n      \"fileTypes\": [\"txt\", \"md\", \"json\", \"xml\"],\n      \"noDisplay\": true\n    }\n  }\n}\n```\n\n`fileTypes` + `noDisplay: true` 实现\"只在文件右键菜单中显示，不在桌面显示\"。\n\n### 7.3 CGI 入口（推荐用于静态页面/Native 应用）\n\n```json\n{\n  \".url\": {\n    \"myapp.Application\": {\n      \"title\": \"我的应用\",\n      \"icon\": \"images/icon_{0}.png\",\n      \"type\": \"iframe\",\n      \"protocol\": \"http\",\n      \"url\": \"/cgi/ThirdParty/myapp/index.cgi/\",\n      \"allUsers\": true\n    }\n  }\n}\n```\n\n> CGI 方案不需要声明 `port` 字段。\n\n### 7.4 入口字段参考\n\n| 字段 | 必填 | 说明 |\n|------|------|------|\n| `title` | ✅ | 入口显示标题（桌面图标名称/右键菜单名称） |\n| `icon` | ✅ | 图标路径（相对 UI 目录），`{0}` 会被替换为 `64` 或 `256` |\n| `type` | ✅ | `url` = 新标签页打开，`iframe` = 桌面窗口内嵌 |\n| `protocol` | ✅ | `http` / `https` / `\"\"`（空字符串=自适应协议；不声明则默认 `http`） |\n| `port` | ✅ | 端口号；CGI 方案无需声明；可使用 `${wizard_port}` 动态配置 (V1.1.8+) |\n| `url` | ✅ | 访问路径（相对路径）；可使用 `${wizard_url}` 动态配置 (V1.1.8+) |\n| `allUsers` | ✅ | `true` = 所有用户可见，`false` = 仅管理员可见 |\n| `fileTypes` | ❌ | 文件右键关联的文件扩展名数组，如 `[\"txt\",\"md\"]` |\n| `noDisplay` | ❌ | `true` = 不在桌面显示，仅右键菜单显示 |\n| `accessPerm` | ❌ | 桌面访问设置权限：`editable` / `readonly` / `hidden` |\n| `gatewaySocket` | ❌ | 统一网关 Socket 文件名 |\n| `gatewayPrefix` | ❌ | 统一网关访问前缀，如 `/app/myapp` |\n\n#### 入口配置文件支持环境变量 (V1.1.8+)\n```json\n{\n  \"myapp.configurable\": {\n    \"title\": \"可配置应用\",\n    \"port\": \"${wizard_port}\",\n    \"url\": \"${wizard_path}\"\n  }\n}\n```\n\n#### 控制字段\n```json\n{\n  \"myapp.advanced\": {\n    \"title\": \"高级功能\",\n    \"control\": {\n      \"accessPerm\": \"readonly\"\n    }\n  }\n}\n```\n\n### 7.5 文件路径参数\n\n通过右键菜单打开文件时，系统自动在 URL 后拼接 `path` 参数：\n```\nhttp://localhost:8080/edit?path=/vol1/Users/admin/Documents/example.txt\n```\n\n### 7.6 CGI 脚本示例\n\n`app/ui/index.cgi` — 通过 Shell 脚本实现静态资源转发：\n\n```bash\n#!/bin/bash\n\n# 【注意】修改为你的静态文件根目录\nBASE_PATH=\"/var/apps/App.Native.HelloFnosAppCenter/target/www\"\n\n# 从 REQUEST_URI 里拿到 index.cgi 后面的路径\nURI_NO_QUERY=\"${REQUEST_URI%%\\?*}\"\nREL_PATH=\"/\"\n\ncase \"$URI_NO_QUERY\" in\n  *index.cgi*)\n    REL_PATH=\"${URI_NO_QUERY#*index.cgi}\"\n    ;;\nesac\n\nif [ -z \"$REL_PATH\" ] || [ \"$REL_PATH\" = \"/\" ]; then\n  REL_PATH=\"/index.html\"\nfi\n\nTARGET_FILE=\"${BASE_PATH}${REL_PATH}\"\n\n# 防御 .. 越级访问\nif echo \"$TARGET_FILE\" | grep -q '\\.\\.'; then\n  echo \"Status: 400 Bad Request\"\n  echo \"Content-Type: text/plain; charset=utf-8\"\n  echo \"\"\n  echo \"Bad Request\"\n  exit 0\nfi\n\nif [ ! -f \"$TARGET_FILE\" ]; then\n  echo \"Status: 404 Not Found\"\n  echo \"Content-Type: text/plain; charset=utf-8\"\n  echo \"\"\n  echo \"404 Not Found: ${REL_PATH}\"\n  exit 0\nfi\n\n# 根据扩展名判断 Content-Type（精简版，完整版见社区参考）\next=\"${TARGET_FILE##*.}\"\ncase \"$ext\" in\n  html|htm)  mime=\"text/html; charset=utf-8\" ;;\n  css)       mime=\"text/css; charset=utf-8\" ;;\n  js)        mime=\"application/javascript; charset=utf-8\" ;;\n  png)       mime=\"image/png\" ;;\n  jpg|jpeg)  mime=\"image/jpeg\" ;;\n  *)         mime=\"application/octet-stream\" ;;\nesac\n\necho \"Content-Type: $mime\"\necho \"\"\ncat \"$TARGET_FILE\"\n```\n\n> 更多 CGI 实现参考：https://github.com/FNOSP/fnosAppCenterCgiCollection\n\n---\n\n## 8. 生命周期脚本 (cmd/)\n\n系统通过调用 `cmd/` 目录下的脚本来管理应用的全生命周期。所有脚本均为 Bash 脚本，全部**必需**（即使只写 `exit 0`）。\n\n> 注意：不要用 `echo` 直接输出错误信息，而是写入 `$TRIM_TEMP_LOGFILE`；不要直接 `exit`，而是返回错误码 `1`。\n\n### 8.1 cmd/main — 启动/停止/状态检查\n\n```bash\n#!/bin/bash\n\ncase $1 in\nstart)\n  # 启动应用的命令，成功返回 0，失败返回 1\n  exit 0\n  ;;\nstop)\n  # 停止应用的命令，成功返回 0，失败返回 1\n  exit 0\n  ;;\nstatus)\n  # 检查应用运行状态，运行中返回 0，未运行返回 3\n  exit 0\n  ;;\n*)\n  exit 1\n  ;;\nesac\n```\n\n#### 应用状态监控\n\n| 返回值 | 含义 |\n|--------|------|\n| `exit 0` | 应用正在运行 |\n| `exit 3` | 应用未运行 |\n| `exit 1` | 执行失败 |\n\n系统会在启动前检查一次，运行期间定期轮询检查。\n\n### 8.2 Native 应用完整启动脚本\n\n```bash\n#!/bin/bash\n\nLOG_FILE=\"${TRIM_PKGVAR}/info.log\"\nPID_FILE=\"${TRIM_PKGVAR}/app.pid\"\nDATA_DIR=\"${TRIM_DATA_SHARE_PATHS%%:*}\"\nCMD=\"DATA_DIR=${DATA_DIR} PORT=5001 node ${TRIM_APPDEST}/server/server.js\"\n\nlog_msg() {\n  echo \"$(date '+%Y-%m-%d %H:%M:%S') - $1\" >> ${LOG_FILE}\n}\n\nstart_process() {\n  if status; then return 0; fi\n  log_msg \"Starting process ...\"\n  bash -c \"${CMD}\" >> ${LOG_FILE} 2>&1 &\n  printf \"%s\" \"$!\" > ${PID_FILE}\n  return 0\n}\n\nstop_process() {\n  log_msg \"Stopping process ...\"\n  if [ -r \"${PID_FILE}\" ]; then\n    pid=$(head -n 1 \"${PID_FILE}\" | tr -d '[:space:]')\n    log_msg \"pid=${pid}\"\n    if ! kill -0 \"${pid}\" 2>/dev/null; then\n      rm -f \"${PID_FILE}\"\n      return\n    fi\n    log_msg \"send TERM signal to PID:${pid}...\"\n    kill -TERM ${pid} >> ${LOG_FILE} 2>&1\n    local count=0\n    while kill -0 \"${pid}\" 2>/dev/null && [ $count -lt 10 ]; do\n      sleep 1; count=$((count + 1))\n    done\n    if kill -0 \"${pid}\" 2>/dev/null; then\n      kill -KILL \"${pid}\"\n    fi\n    rm -f \"${PID_FILE}\"\n  fi\n}\n\nstatus() {\n  if [ -f \"${PID_FILE}\" ]; then\n    pid=$(head -n 1 \"${PID_FILE}\" | tr -d '[:space:]')\n    if kill -0 \"${pid}\" 2>/dev/null; then return 0; fi\n    rm -f \"${PID_FILE}\"\n  fi\n  return 1\n}\n\ncase $1 in\n  start)  start_process ;;\n  stop)   stop_process ;;\n  status) if status; then exit 0; else exit 3; fi ;;\n  *)      exit 1 ;;\nesac\n```\n\n### 8.3 运行环境配置\n\n#### Node.js\n```bash\n# 在 cmd 脚本中配置，可选版本：nodejs_v22 / v20 / v18 / v16 / v14\nexport PATH=/var/apps/nodejs_v22/target/bin:$PATH\nnode -v\nnpm -v\n```\n\n#### Python\n```bash\n# 可选版本：python312 / 311 / 310 / 39 / 38\nexport PATH=/var/apps/python312/target/bin:$PATH\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\n```\n\n#### Java\n```bash\n# 可选版本：java-21-openjdk / java-17-openjdk / java-11-openjdk\nexport PATH=/var/apps/java-21-openjdk/target/bin:$PATH\njava --version\n```\n\n### 8.4 应用配置与文件写入\n\n飞牛 NAS 应用安装后，系统会在多个目录下为应用分配存储空间。不同目录的用途和权限差异很大，理解它们对写出健壮的应用至关重要。\n\n#### 目录一览\n\n| 目录 | 权限 | 用途 | 系统写入 | 应用写入 |\n|------|------|------|----------|----------|\n| `/vol1/@appconf/{appname}/settings.conf` | 应用用户 | **应用配置**（端口、路径等） | ✅（向导/设置页面） | ✅ |\n| `/vol1/@appdata/{appname}/` | 应用用户 | **应用数据**（运行时日志、数据库等） | ✘ | ✅ |\n| `/vol1/@apphome/{appname}/` | 应用用户 | **应用家目录** | ✘ | ✅ |\n| `/vol1/@apptemp/{appname}/` | 应用用户 | **缓存/临时文件** | ✘ | ✅ |\n| `/vol1/@appshare/{appname}/` | 应用用户 | **共享目录**（同 `TRIM_DATA_SHARE_PATHS`） | ✘ | ✅ |\n| `/vol1/@appcenter/{appname}/` | 应用用户 | **应用本身代码（只读）** | ✘（可手动修改但不建议） | ✘ |\n\n> 存储在 `/vol1/@appcenter/` 下的是应用包的运行目录（`app/server/` 等），不是 `@appconf/`。\n\n#### settings.conf：应用配置的核心\n\n`/vol1/@appconf/{appname}/settings.conf` 是 NAS 系统与应用之间的配置桥梁。它的格式为 `KEY=VALUE` 纯文本，**系统会在以下时机自动重写此文件**：\n\n1. **安装/更新**时——写入 `wizard/` 向导收集的用户输入（以 `wizard_` 为前缀的环境变量）\n2. **应用设置页面**修改时——写入授权文件夹、端口等系统级配置\n3. 应用本身也可以主动写入此文件，但要注意**系统下次重写时会覆盖**\n\n#### ⚠️ 常见陷阱：系统重写覆盖问题\n\n```\n# settings.conf 被系统重写后：\nMUSIC_PATH=/vol1/1000/Music       ← 路径被系统改写，可能与真实路径名不符\nPORT=5200\n```\n\n已知问题：\n1. **路径命名差异**——用户实际选的文件夹名（如 `music11` / `music22`）被系统写成了 `Music`（大小写不一致或截断）\n2. **授权文件夹丢失**——用户授权了多个数据目录，但系统只保存了第一个\n3. **每次修改设置都会覆盖**——手动改 `settings.conf` 后，只要用户进一次应用设置页面就会被重置\n4. **`data-share` 可能为空**——`config/resource` 中 `data-share.shares` 数组不更新，导致 `TRIM_DATA_ACCESSIBLE_PATHS` 环境变量为空\n\n#### 应对策略\n\n**策略一：应用启动时自读 settings.conf（推荐，最通用）**\n\n不依赖 `cmd/main` 传环境变量，让应用服务进程启动时**自己读取 `settings.conf`**。这样不管 NAS 系统以何种方式拉起进程（自动启动、手动重启）都能正确拿到配置。\n\n```javascript\n// Node.js — 在读取 PORT / MUSIC_PATH 之前执行\nvar path = require('path');\nvar fs = require('fs');\n\n// settings.conf 实际位置：\n//   @appconf 的挂载点 = __dirname 减去 @appcenter 后的相对路径\nvar confPath = path.resolve(__dirname, '../../../@appconf/YOUR_APP_NAME/settings.conf');\ntry {\n  var confContent = fs.readFileSync(confPath, 'utf-8');\n  var confLines = confContent.split(String.fromCharCode(10));\n  for (var i = 0; i < confLines.length; i++) {\n    var line = confLines[i].trim();\n    if (line && !line.startsWith('#')) {\n      var eqIdx = line.indexOf('=');\n      if (eqIdx > 0) {\n        var key = line.substring(0, eqIdx).trim();\n        var val = line.substring(eqIdx + 1).trim();\n        if (process.env[key] === undefined) process.env[key] = val;\n      }\n    }\n  }\n} catch(e) { /* 配置文件不存在时忽略 */ }\n\nconst PORT = process.env.PORT || 6688;      // 已被 settings.conf 注入\n```\n\n> 注意：这段代码必须放在 `PORT` 声明**之前**，否则环境变量已经读过了，兜底逻辑不会生效。\n\n```python\n# Python 版本\nimport os\n\ndef load_settings():\n    config_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))),\n                               '..', '@appconf', 'YOUR_APP_NAME', 'settings.conf')\n    # 更准确的：从 __file__ 向上走到 @appcenter，再到 @appconf\n    config_path = os.path.normpath(config_path)\n    if not os.path.exists(config_path):\n        return\n    with open(config_path, 'r') as f:\n        for line in f:\n            line = line.strip()\n            if not line or line.startswith('#'):\n                continue\n            if '=' in line:\n                key, val = line.split('=', 1)\n                key = key.strip()\n                val = val.strip()\n                if key not in os.environ:\n                    os.environ[key] = val\n\nload_settings()\nPORT = int(os.environ.get('PORT', 6688))\n```\n\n**策略二：只用 cmd/main 传环境变量（较脆弱）**\n\n```bash\n# cmd/main 中正确的做法：读 settings.conf + 传环境变量\n. /vol1/@appconf/YOUR_APP_NAME/settings.conf\nexport PORT MUSIC_PATH\nnode /vol1/@appcenter/YOUR_APP_NAME/server/server.js\n```\n\n局限：NAS 自动启动时可能不经过 `cmd/main`，环境变量仍然丢失。\n\n**策略三：验证路径有效性，失败时友好报错**\n\n```javascript\nvar musicPaths = (process.env.MUSIC_PATH || '').split(':').filter(Boolean);\nvar validPaths = [];\nmusicPaths.forEach(function(p) {\n  try {\n    if (fs.statSync(p).isDirectory()) {\n      validPaths.push(p);\n    }\n  } catch(e) {\n    console.error('Music directory not found:', p);\n  }\n});\nif (validPaths.length === 0) {\n  console.error('No valid music folder. Please set it in App Settings.');\n  process.exit(1);\n}\n```\n\n#### 实践建议\n\n1. **首选策略一**（应用自读 settings.conf）+ **策略三**（路径验证），覆盖所有启动场景\n2. 不要依赖 `TRIM_DATA_ACCESSIBLE_PATHS` 环境变量——它可能为空\n3. 对于 Native 应用，建议在 `cmd/main` 和 app 代码中都实现配置读取，双重保险\n4. 如果发现路径不匹配（如实际文件夹 `music11` 但配置写 `Music`），先确认是否是 NAS 系统写入的问题，**不要**在代码里做硬编码的路径猜测\n\n#### ⚠️ 端口变更的实战陷阱\n\n##### 问题描述\n用户在安装向导/应用设置中修改端口（`wizard_port`）后，应用重启仍然监听默认端口。\n\n##### 根因分析\n飞牛系统通过 `TRIM_SERVICE_PORT` 环境变量（来源：manifest 的 `service_port` 字段）将端口注入 `cmd/main`。但关键问题在于：\n\n- **`service_port` 只在打包时固定**，用户在安装向导或应用设置中修改端口后，`service_port` **不会自动更新**\n- `TRIM_SERVICE_PORT` 的值在首次安装后即固定，不会因 `wizard_port` 变更而重新注入\n- `config_callback` 虽然能通过 `$wizard_port` 变量拿到新端口，但直接调用 `cmd/main start` 时传给 Node 的仍然是旧的 `TRIM_SERVICE_PORT`\n\n##### 修复方案：三级端口优先级\n`cmd/main` 中端口获取应按以下优先级：\n\n```bash\n# 第一优先级：settings.conf 中持久化的 PORT（来自安装向导 / 应用设置的 wizard_port）\nif [ -f \"${TRIM_PKGETC}/settings.conf\" ]; then\n  . \"${TRIM_PKGETC}/settings.conf\"\nfi\n# 第二优先级：TRIM_SERVICE_PORT（系统注入，来自 manifest service_port）\n# 第三优先级：默认值\nPORT=\"${PORT:-${TRIM_SERVICE_PORT:-8080}}\"\n```\n\n##### 完整工作流\n1. **安装时**：`install_callback` 将 `$wizard_port` 写入 `${TRIM_PKGETC}/settings.conf`\n2. **修改端口时**（应用设置页面）：`config_callback` 将 `$wizard_port` 写入 `settings.conf`，然后重启\n3. **任何后续启动**（系统自动启动、手动重启）：`cmd/main` 优先读取 `settings.conf` 中的 `PORT`，不受 `TRIM_SERVICE_PORT` 旧值影响\n\n##### 配套的 config_callback\n```bash\nPORT=\"${wizard_port:-8080}\"\n\n# 先持久化端口到 settings.conf\nmkdir -p \"${TRIM_PKGETC}\"\ncat > \"${TRIM_PKGETC}/settings.conf\" <<EOF\nPORT=${PORT}\nEOF\n\n# 然后重启（此时不需要传 TRIM_SERVICE_PORT，因为 cmd/main 会自读 settings.conf）\nbash \"${0%/*}/main\" stop\nsleep 1\nbash \"${0%/*}/main\" start\n```\n\n##### 要点\n- **`settings.conf` 的自定义写入要放到 `install_callback` 和 `config_callback` 中**，不要在 `install_init` 或其他脚本中写，否则可能在安装流程中被系统覆盖\n- `TRIM_SERVICE_PORT` 仍然可以作为 fallback 使用，但**不要把它作为唯一的端口来源**\n- 如果 `settings.conf` 被系统重写覆盖了 `PORT`（某些系统版本已知问题），检查端口是否仍然正确，必要时在 `config_init` 中添加校验\n\n### 8.4 Docker 应用的 main 脚本\n\nDocker 应用的启停由系统通过 compose 管理，但需要定义状态检查：\n\n```bash\n#!/bin/bash\n\nFILE_PATH=\"${TRIM_APPDEST}/docker/docker-compose.yaml\"\n\nis_docker_running () {\n  DOCKER_NAME=\"\"\n  if [ -f \"$FILE_PATH\" ]; then\n    DOCKER_NAME=$(cat $FILE_PATH | grep \"container_name\" | awk -F ':' '{print $2}' | xargs)\n    echo \"DOCKER_NAME is set to: $DOCKER_NAME\"\n  fi\n  if [ -n \"$DOCKER_NAME\" ]; then\n    docker inspect $DOCKER_NAME | grep -q \"\\\"Status\\\": \\\"running\\\",\" || exit 1\n    return\n  fi\n}\n\ncase $1 in\n  start)  exit 0 ;;  # compose 管理，无需额外操作\n  stop)   exit 0 ;;  # compose 管理，无需额外操作\n  status) if is_docker_running; then exit 0; else exit 3; fi ;;\n  *)      exit 1 ;;\nesac\n```\n\n### 8.5 错误异常展示处理（V1.1.8+）\n\n向 `$TRIM_TEMP_LOGFILE` 写入错误信息并返回错误码 `1`，系统会自动以 Dialog 对话框展示给用户：\n\n```bash\n# ✅ 正确做法\necho \"配置文件不存在，应用启动失败！\" > \"${TRIM_TEMP_LOGFILE}\"\nexit 1\n\n# ❌ 错误做法\n# echo \"配置文件不存在，应用启动失败！\"\n# exit 1\n```\n\n### 8.6 生命周期事件说明\n\n| 事件 | 脚本 | 时机 |\n|------|------|------|\n| 安装前 | `install_init` | 文件解压前，可做环境检查、依赖安装 |\n| 安装后 | `install_callback` | 文件解压后，可做初始化配置 |\n| 卸载前 | `uninstall_init` | 停止应用后，卸载前 |\n| 卸载后 | `uninstall_callback` | 可清理剩余数据和目录 |\n| 升级前 | `upgrade_init` | 更新处理前 |\n| 升级后 | `upgrade_callback` | 可做数据库升级、配置迁移 |\n| 配置变更前 | `config_init` | 用户保存配置后，重启前 |\n| 配置变更后 | `config_callback` | 可监听配置变化调整运行逻辑 |\n\n#### 卸载时保留/删除数据\n系统默认保留 `var` 和 `shares` 目录。如果希望在卸载向导中让用户选择是否删除数据，在 `wizard/uninstall` 中配置选项，在 `cmd/uninstall_callback` 中根据用户选择清理：\n\n```bash\nif [ \"$wizard_data_action\" = \"delete\" ]; then\n  rm -rf \"${TRIM_PKGVAR}\" \"${TRIM_DATA_SHARE_PATHS}\"\nfi\n```\n\n---\n\n## 9. 环境变量参考\n\n### 9.1 系统环境变量\n\n| 变量 | 说明 |\n|------|------|\n| `$TRIM_APPNAME` | 应用名称 |\n| `$TRIM_APPVER` | 应用版本号 |\n| `$TRIM_APPDEST` | 应用可执行文件目录（target） |\n| `$TRIM_PKGETC` | 配置文件目录（etc） |\n| `$TRIM_PKGVAR` | 运行时数据目录（var） |\n| `$TRIM_TEMP_LOGFILE` | 临时日志文件路径（用户可见） |\n| `$TRIM_SERVICE_PORT` | 服务端口 |\n| `$TRIM_USERNAME` | 应用专用用户名 |\n| `$TRIM_RUN_USERNAME` | 当前运行用户（`root` 或应用用户） |\n| `$TRIM_DATA_SHARE_PATHS` | 数据共享目录路径列表（冒号分隔） |\n| `$TRIM_API_TOKEN` | 开放 API 后端调用令牌，系统调用应用脚本时自动注入；每次调用从环境变量读取，切勿持久化或暴露给前端 |\n\n### 9.2 向导输入变量\n\n用户在向导中的选择会变成同名的环境变量，在脚本中可直接使用：\n\n```bash\nADMIN_USERNAME=\"$wizard_admin_username\"\nDATABASE_TYPE=\"$wizard_database_type\"\nAPP_PORT=\"$wizard_app_port\"\n```\n\n### 9.3 应用入口中的动态变量（V1.1.8+）\n\n入口配置文件 `app/ui/config` 中可使用 `${variable_name}` 动态引用向导参数：\n\n```json\n{\n  \"port\": \"${wizard_port}\",\n  \"url\": \"${wizard_path}\"\n}\n```\n\n---\n\n## 10. 向导配置 (wizard/)\n\n向导是用户与应用交互的\"引导员\"，JSON 格式，每个文件是一个 JSON 数组（多个步骤）。\n\n### 10.1 向导类型\n\n| 文件 | 用途 |\n|------|------|\n| `wizard/install` | 安装时的配置界面 |\n| `wizard/uninstall` | 卸载时的确认界面 |\n| `wizard/upgrade` | 更新时的配置界面 |\n| `wizard/config` | 设置时的配置界面 |\n\n### 10.2 向导文件结构\n\n```json\n[\n  {\n    \"stepTitle\": \"第一步标题\",\n    \"items\": [\n      { 表单项 1 },\n      { 表单项 2 }\n    ]\n  },\n  {\n    \"stepTitle\": \"第二步标题\",\n    \"items\": [\n      { 表单项 3 }\n    ]\n  }\n]\n```\n\n### 10.3 表单项类型\n\n#### text — 文本输入\n```json\n{\n  \"type\": \"text\",\n  \"field\": \"wizard_username\",\n  \"label\": \"用户名\",\n  \"initValue\": \"admin\",\n  \"rules\": [\n    { \"required\": true, \"message\": \"请输入用户名\" },\n    { \"min\": 3, \"max\": 20, \"message\": \"长度应在3-20字符之间\" }\n  ]\n}\n```\n\n#### password — 密码输入\n```json\n{\n  \"type\": \"password\",\n  \"field\": \"wizard_password\",\n  \"label\": \"管理员密码\",\n  \"rules\": [\n    { \"required\": true, \"message\": \"请输入密码\" },\n    { \"min\": 6, \"message\": \"密码长度不能少于6位\" }\n  ]\n}\n```\n\n#### radio — 单选\n```json\n{\n  \"type\": \"radio\",\n  \"field\": \"wizard_install_type\",\n  \"label\": \"安装类型\",\n  \"initValue\": \"standard\",\n  \"options\": [\n    { \"label\": \"标准安装\", \"value\": \"standard\" },\n    { \"label\": \"自定义安装\", \"value\": \"custom\" }\n  ],\n  \"rules\": [{ \"required\": true, \"message\": \"请选择安装类型\" }]\n}\n```\n\n#### checkbox — 多选\n```json\n{\n  \"type\": \"checkbox\",\n  \"field\": \"wizard_modules\",\n  \"label\": \"安装模块\",\n  \"initValue\": [\"web\", \"api\"],\n  \"options\": [\n    { \"label\": \"Web界面\", \"value\": \"web\" },\n    { \"label\": \"API接口\", \"value\": \"api\" },\n    { \"label\": \"数据库\", \"value\": \"database\" }\n  ],\n  \"rules\": [{ \"required\": true, \"message\": \"请至少选择一个模块\" }]\n}\n```\n\n#### select — 下拉选择\n```json\n{\n  \"type\": \"select\",\n  \"field\": \"wizard_database_type\",\n  \"label\": \"数据库类型\",\n  \"initValue\": \"sqlite\",\n  \"options\": [\n    { \"label\": \"SQLite (推荐)\", \"value\": \"sqlite\" },\n    { \"label\": \"MySQL\", \"value\": \"mysql\" },\n    { \"label\": \"PostgreSQL\", \"value\": \"postgresql\" }\n  ],\n  \"rules\": [{ \"required\": true, \"message\": \"请选择数据库类型\" }]\n}\n```\n\n#### switch — 开关\n```json\n{\n  \"type\": \"switch\",\n  \"field\": \"wizard_enable_backup\",\n  \"label\": \"启用自动备份\",\n  \"initValue\": \"true\"\n}\n```\n\n#### tips — 提示文本（不收集输入）\n```json\n{\n  \"type\": \"tips\",\n  \"helpText\": \"请阅读 <a target=\\\"_blank\\\" href=\\\"https://example.com/privacy\\\">隐私政策</a>。\"\n}\n```\n\n### 10.4 验证规则\n\n| 规则 | 示例 |\n|------|------|\n| 必填 | {\\\"required\\\": true, \\\"message\\\": \\\"此字段不能为空\\\"} |\n| 最小长度 | {\\\"min\\\": 3, \\\"message\\\": \\\"长度不能少于3\\\"} |\n| 最大长度 | {\\\"max\\\": 50, \\\"message\\\": \\\"长度不能超过50\\\"} |\n| 精确长度 | {\\\"len\\\": 6, \\\"message\\\": \\\"请输入6位验证码\\\"} |\n| 正则 | {\\\"pattern\\\": \\\"^[a-zA-Z0-9_]+$\\\", \\\"message\\\": \\\"只能含字母数字下划线\\\"} |\n\n### 10.5 安装向导完整示例\n\n```json\n[\n  {\n    \\\"stepTitle\\\": \\\"欢迎安装\\\",\n    \\\"items\\\": [\n      {\n        \\\"type\\\": \\\"tips\\\",\n        \\\"helpText\\\": \\\"欢迎使用我们的应用！在开始使用前，请阅读并同意我们的服务条款。\\\"\n      },\n      {\n        \\\"type\\\": \\\"switch\\\",\n        \\\"field\\\": \\\"wizard_agree_terms\\\",\n        \\\"label\\\": \\\"我已阅读并同意服务条款\\\",\n        \\\"rules\\\": [\n          { \\\"required\\\": true, \\\"message\\\": \\\"请同意服务条款\\\" }\n        ]\n      }\n    ]\n  },\n  {\n    \\\"stepTitle\\\": \\\"创建管理员账号\\\",\n    \\\"items\\\": [\n      {\n        \\\"type\\\": \\\"text\\\",\n        \\\"field\\\": \\\"wizard_admin_username\\\",\n        \\\"label\\\": \\\"管理员用户名\\\",\n        \\\"initValue\\\": \\\"admin\\\",\n        \\\"rules\\\": [\n          { \\\"required\\\": true, \\\"message\\\": \\\"请输入管理员用户名\\\" },\n          { \\\"pattern\\\": \\\"^[a-zA-Z0-9_]+$\\\", \\\"message\\\": \\\"只能包含字母、数字和下划线\\\" }\n        ]\n      },\n      {\n        \\\"type\\\": \\\"password\\\",\n        \\\"field\\\": \\\"wizard_admin_password\\\",\n        \\\"label\\\": \\\"管理员密码\\\",\n        \\\"rules\\\": [\n          { \\\"required\\\": true, \\\"message\\\": \\\"请输入密码\\\" },\n          { \\\"min\\\": 8, \\\"message\\\": \\\"密码长度不能少于8位\\\" }\n        ]\n      },\n      {\n        \\\"type\\\": \\\"password\\\",\n        \\\"field\\\": \\\"wizard_admin_password_confirm\\\",\n        \\\"label\\\": \\\"确认密码\\\",\n        \\\"rules\\\": [\n          { \\\"required\\\": true, \\\"message\\\": \\\"请确认密码\\\" }\n        ]\n      }\n    ]\n  },\n  {\n    \\\"stepTitle\\\": \\\"应用配置\\\",\n    \\\"items\\\": [\n      {\n        \\\"type\\\": \\\"select\\\",\n        \\\"field\\\": \\\"wizard_database_type\\\",\n        \\\"label\\\": \\\"数据库类型\\\",\n        \\\"initValue\\\": \\\"sqlite\\\",\n        \\\"options\\\": [\n          { \\\"label\\\": \\\"SQLite (推荐，无需额外配置)\\\", \\\"value\\\": \\\"sqlite\\\" },\n          { \\\"label\\\": \\\"MySQL\\\", \\\"value\\\": \\\"mysql\\\" }\n        ]\n      },\n      {\n        \\\"type\\\": \\\"text\\\",\n        \\\"field\\\": \\\"wizard_app_port\\\",\n        \\\"label\\\": \\\"应用端口\\\",\n        \\\"initValue\\\": \\\"8080\\\",\n        \\\"rules\\\": [\n          { \\\"required\\\": true, \\\"message\\\": \\\"请输入端口号\\\" },\n          { \\\"pattern\\\": \\\"^[0-9]+$\\\", \\\"message\\\": \\\"端口号必须是数字\\\" }\n        ]\n      }\n    ]\n  }\n]\n```\n\n### 10.6 卸载向导示例\n\n```json\n[\n  {\n    \\\"stepTitle\\\": \\\"确认卸载\\\",\n    \\\"items\\\": [\n      {\n        \\\"type\\\": \\\"tips\\\",\n        \\\"helpText\\\": \\\"您即将卸载此应用。请选择如何处理应用数据：\\\"\n      },\n      {\n        \\\"type\\\": \\\"radio\\\",\n        \\\"field\\\": \\\"wizard_data_action\\\",\n        \\\"label\\\": \\\"数据保留选项\\\",\n        \\\"initValue\\\": \\\"keep\\\",\n        \\\"options\\\": [\n          { \\\"label\\\": \\\"保留数据（推荐）- 将来重新安装时可恢复\\\", \\\"value\\\": \\\"keep\\\" },\n          { \\\"label\\\": \\\"删除所有数据 - 此操作不可恢复！\\\", \\\"value\\\": \\\"delete\\\" }\n        ],\n        \\\"rules\\\": [\n          { \\\"required\\\": true, \\\"message\\\": \\\"请选择数据保留选项\\\" }\n        ]\n      },\n      {\n        \\\"type\\\": \\\"tips\\\",\n        \\\"helpText\\\": \\\"<strong>警告：</strong> 选择删除数据后，所有应用数据将永久丢失，无法恢复。\\\"\n      }\n    ]\n  }\n]\n```\n\n### 10.7 获取用户输入\n\n```bash\n# 用户输入直接作为环境变量使用\nADMIN_USERNAME=\\\"\\$wizard_admin_username\\\"\nADMIN_PASSWORD=\\\"\\$wizard_admin_password\\\"\nDATABASE_TYPE=\\\"\\$wizard_database_type\\\"\nAPP_PORT=\\\"\\$wizard_app_port\\\"\n```\n\n## 11. 图标规范\n\n### 11.1 包文件图标（根目录）\n\n| 文件 | 尺寸 | 格式 | 必填 |\n|------|------|------|------|\n| `ICON.PNG` | 64×64 像素 | PNG，不透明 | ✅ |\n| `ICON_256.PNG` | 256×256 像素 | PNG，不透明 | ✅ |\n\n> 含圆角矩形背景的 PSD 源文件可下载：https://static.fnnas.com/appcenter-marketing/fnpack_ICON_256.zip\n\n### 11.2 UI 入口图标（app/ui/images/）\n\n| 文件 | 尺寸 | 命名规则 |\n|------|------|----------|\n| `icon_64.png` | 64×64 | **小写**，配置中 `{0}` 替换为尺寸 |\n| `icon_256.png` | 256×256 | 如 `images/icon_{0}.png` → 自动选取 |\n\n---\n\n## 12. Docker 应用构建详解\n\n### 12.1 创建项目\n\n```bash\nfnpack create my-app --template docker\n```\n\n### 12.2 目录结构\n\n```\nmy-app/\n├── app/\n│   ├── docker/\n│   │   └── docker-compose.yaml\n│   └── ui/\n│       ├── images/\n│       └── config\n├── manifest\n├── cmd/\n├── config/\n│   ├── privilege\n│   └── resource\n├── wizard/\n├── LICENSE\n├── ICON.PNG\n└── ICON_256.PNG\n```\n\n### 12.3 docker-compose.yaml 示例\n\n```yaml\nversion: '3.8'\n\nservices:\n  web:\n    image: myapp:latest\n    container_name: myapp-web\n    ports:\n      - \"${TRIM_SERVICE_PORT}:80\"\n    volumes:\n      - \"${TRIM_PKGVAR}:/app/data\"\n      - \"${TRIM_DATA_SHARE_PATHS}:/app/shares\"\n    environment:\n      - DB_HOST=db\n      - APP_PORT=${TRIM_SERVICE_PORT}\n\n  db:\n    image: mysql:8.0\n    container_name: myapp-db\n    environment:\n      - MYSQL_ROOT_PASSWORD=${wizard_db_password}\n      - MYSQL_DATABASE=myapp\n    volumes:\n      - db_data:/var/lib/mysql\n\nvolumes:\n  db_data:\n```\n\n> `docker-compose.yaml` 支持使用全部系统环境变量和向导变量。\n> 系统统一管理 compose 的启动/停止，`cmd/main` 只需要实现 status 检查。\n\n### 12.4 资源配置\n\n```json\n{\n  \"docker-project\": {\n    \"projects\": [\n      {\n        \"name\": \"myapp-stack\",\n        \"path\": \"docker\"\n      }\n    ]\n  }\n}\n```\n\n---\n\n## 13. 统一网关（fnOS V1.1.3100+）\n\n统一网关为应用提供稳定的访问入口，无需新增端口监听。HTTP 和 WebSocket 均可接入。\n\n### 13.1 接入方式\n\n在 `app/ui/config` 中声明 `gatewayPrefix` 和 `gatewaySocket`：\n\n```json\n{\n  \".url\": {\n    \"myapp.main\": {\n      \"title\": \"我的应用\",\n      \"icon\": \"images/icon_{0}.png\",\n      \"type\": \"iframe\",\n      \"protocol\": \"\",\n      \"gatewaySocket\": \"app.sock\",\n      \"gatewayPrefix\": \"/app/myapp\",\n      \"url\": \"/app/myapp\",\n      \"allUsers\": true\n    }\n  }\n}\n```\n\n#### 字段说明\n| 字段 | 条件 | 说明 |\n|------|------|------|\n| `gatewayPrefix` | 两者均非空时注册 | 网关访问前缀，格式 `/app/{appname}/{customPath}` 或 `/app/{appname}`，不能包含 `.` |\n| `gatewaySocket` | 两者均非空时注册 | Socket 文件名，如 `app.sock`，放在应用 target 目录 |\n\n### 13.2 WebSocket 支持\n\n```json\n{\n  \"myapp.chat\": {\n    \"title\": \"聊天应用\",\n    \"icon\": \"images/icon_{0}.png\",\n    \"type\": \"iframe\",\n    \"protocol\": \"\",\n    \"gatewaySocket\": \"chat.sock\",\n    \"gatewayPrefix\": \"/app/chat\",\n    \"url\": \"/app/chat\",\n    \"allUsers\": true\n  }\n}\n```\n\n前端连接示例：\n```javascript\nconst wsProtocol = window.location.protocol === \"https:\" ? \"wss:\" : \"ws:\";\nconst wsUrl = `${wsProtocol}//${window.location.host}/app/chat/ws`;\nconst socket = new WebSocket(wsUrl);\n```\n\n> WebSocket 路由建议固定为网关前缀下的子路径（如 `/ws`），不要信任客户端主动上报的用户 ID。\n\n### 13.3 登录认证\n\n统一网关转发请求前完成登录态校验，认证通过后增加以下 Header：\n\n| Header | 说明 | 示例 |\n|--------|------|------|\n| `X-Trim-Userid` | 当前登录用户 UID | `1000` |\n| `X-Trim-Isadmin` | 是否管理员 | `true` / `false` |\n| `X-Trim-Username` | 当前登录用户名 | `admin` |\n\nNode.js 获取用户信息：\n```javascript\nfunction getGatewayUser(req) {\n  return {\n    uid: req.headers[\"x-trim-userid\"],\n    isAdmin: req.headers[\"x-trim-isadmin\"] === \"true\",\n    username: req.headers[\"x-trim-username\"]\n  };\n}\n```\n\n> 应用仍需要自己的权限判断逻辑（数据隔离、管理员接口、高风险操作校验）。\n\n### 13.4 不鉴权接口\n\n公开资源、OAuth 回调等无需登录态的特殊接口：\n- 只开放必要路径\n- 只允许必要 HTTP 方法\n- 不返回用户敏感信息\n- 不提供写入/删除等高危能力\n\n### 13.5 静态文件安全\n- 路径标准化处理\n- 禁止 `..` 访问上级目录\n- 限制可访问目录范围\n- 不暴露配置文件、密钥、数据库\n- 可下载文件类型做白名单控制\n\n---\n\n## 14. 运行时环境与中间件\n\n### 14.1 运行时环境\n\n通过 `manifest` 的 `install_dep_apps` 声明依赖，系统确保安装和启动时目标环境已就绪。\n\n| 运行时 | manifest 声明 | PATH 配置 |\n|--------|---------------|-----------|\n| Node.js v22 | `install_dep_apps=nodejs_v22` | `export PATH=/var/apps/nodejs_v22/target/bin:$PATH` |\n| Node.js v20 | `install_dep_apps=nodejs_v20` | `export PATH=/var/apps/nodejs_v20/target/bin:$PATH` |\n| Node.js v18 | `install_dep_apps=nodejs_v18` | `export PATH=/var/apps/nodejs_v18/target/bin:$PATH` |\n| Python 3.12 | `install_dep_apps=python312` | `export PATH=/var/apps/python312/target/bin:$PATH` |\n| Python 3.11 | `install_dep_apps=python311` | `export PATH=/var/apps/python311/target/bin:$PATH` |\n| Python 3.10 | `install_dep_apps=python310` | `export PATH=/var/apps/python310/target/bin:$PATH` |\n| Java 21 | `install_dep_apps=java-21-openjdk` | `export PATH=/var/apps/java-21-openjdk/target/bin:$PATH` |\n| Java 17 | `install_dep_apps=java-17-openjdk` | `export PATH=/var/apps/java-17-openjdk/target/bin:$PATH` |\n\n**Python 虚拟环境最佳实践：**\n```bash\nexport PATH=/var/apps/python312/target/bin:$PATH\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\n```\n\n### 14.2 中间件服务\n\n| 中间件 | manifest 声明 | 连接信息 |\n|--------|---------------|----------|\n| Redis | `install_dep_apps=redis` | `127.0.0.1:6379` |\n| MinIO | `install_dep_apps=minio` | `127.0.0.1:9000` |\n| RabbitMQ | `install_dep_apps=rabbitmq` | `127.0.0.1:5672`，默认 `guest/guest` |\n| MariaDB | `install_dep_apps=mariaDB` | 即将上线 |\n\n#### Redis 使用示例（Python）\n```python\nimport redis\n\npool = redis.ConnectionPool(\n    host='127.0.0.1', port=6379, db=1,\n    decode_responses=True, max_connections=10\n)\nclient = redis.Redis(connection_pool=pool)\nclient.lpush('my_list', 'item1', 'item2')\nitems = client.lrange('my_list', 0, -1)\n```\n\n#### MinIO 使用示例（Python）\n```python\nfrom minio import Minio\n\nclient = Minio(\n    endpoint=\"127.0.0.1:9000\",\n    access_key=\"your_access_key\",\n    secret_key=\"your_secret_key\",\n    secure=False\n)\nif not client.bucket_exists(\"my-bucket\"):\n    client.make_bucket(\"my-bucket\")\n```\n\n#### RabbitMQ 使用示例（Python）\n```python\nimport pika\n\nconnection = pika.BlockingConnection(pika.ConnectionParameters(\n    host=\"127.0.0.1\", port=5672,\n    virtual_host=\"/\",\n    credentials=pika.PlainCredentials(\"guest\", \"guest\")\n))\nchannel = connection.channel()\nchannel.queue_declare(queue=\"my_queue\")\nchannel.basic_publish(exchange=\"\", routing_key=\"my_queue\", body=\"Hello\")\n```\n\n---\n\n## 15. 应用依赖关系\n\n### 15.1 声明依赖\n\n在 `manifest` 中使用 `install_dep_apps` 字段：\n\n```ini\n# 格式：app1>2.2.2:app2:app3\n# > 表示最低版本要求\n# : 分隔多个依赖\ninstall_dep_apps = dep2:dep1\n```\n\n### 15.2 依赖检查逻辑\n\n| 操作 | 行为 |\n|------|------|\n| 安装/启用 | 检查依赖是否已安装启用，未安装则自动安装，未启用则自动启用 |\n| 停用/卸载 | 检查是否有其他应用依赖本应用，有则提示自动停用 |\n| 更新 | 检查是否有其他应用依赖，有则在更新期间自动停用 |\n\n### 15.3 依赖顺序\n\n自动安装和启用的顺序**从后往前**：\n```ini\n# 先安装 dep1，后安装 dep2\ninstall_dep_apps = dep2:dep1\n```\n\n### 15.4 嵌套依赖\n\n> 应用中心仅对一层依赖进行检查，不做递归检查。\n\n如果 A 依赖 B，B 依赖 C，则 A 需要同时声明 B 和 C：\n```ini\ninstall_dep_apps = depB:depC\n```\n\n---\n\n## 16. Native 应用完整构建流程\n\n### 16.1 开发 → 打包流程\n\n1. 开发应用代码（如 Node.js + Express）\n2. 编译打包到 `dist/` 目录\n3. `fnpack create <appname>` 创建打包目录\n4. 将编译产物复制到 `app/server/`\n5. 编辑 `manifest`、`config/privilege`、`config/resource`\n6. 编写 `cmd/main` 生命周期脚本\n7. 配置 `app/ui/config` 入口\n8. 添加 `app/ui/images/icon_64.png` 和 `icon_256.png`\n9. 更新根目录 `ICON.PNG` 和 `ICON_256.PNG`\n10. `fnpack build` 打包\n\n### 16.2 集成到编译脚本\n\n```javascript\n// scripts/build-combined.js（Node.js 项目）\nconst packDir = path.join(root, 'fnnas.notepad')\nconst packServerDir = path.join(packDir, 'app', 'server');\nrun(`rm -rf ${packServerDir}`);\nrun(`mkdir ${packServerDir}`);\nrun(`cp -r ${outDir}/* ${packServerDir}/`);\nrun(`fnpack build -d ${packDir}`);\n```\n\n### 16.3 纯静态页面 Native 应用\n\n对于无后台服务的静态页面应用，`cmd/main` 只需：\n```bash\n#!/bin/bash\n\ncase $1 in\nstart)   exit 0 ;;  # 无进程需要启动\nstop)    exit 0 ;;  # 无进程需要停止\nstatus)  exit 0 ;;  # 静态页面始终\"运行中\"\n*)       exit 1 ;;\nesac\n```\n\n> 此时的 CGI 脚本负责处理 HTTP 请求，从 `app/www/` 中读取并返回静态文件。\n\n---\n\n## 17. 打包与校验\n\n### 17.1 基本打包\n\n```bash\ncd myapp\nfnpack build                    # 当前目录打包\nfnpack build --directory <path> # 指定目录打包\n```\n\n### 17.2 打包校验规则\n\n| 路径 | 校验要求 |\n|------|----------|\n| `manifest` | 必须存在，必选字段存在 |\n| `config/privilege` | 必须存在，符合 JSON 格式 |\n| `config/resource` | 必须存在，符合 JSON 格式 |\n| `ICON.PNG` | 必须存在 |\n| `ICON_256.PNG` | 必须存在 |\n| `app/` | 目录必须存在 |\n| `cmd/` | 目录必须存在 |\n| `wizard/` | 目录必须存在 |\n| `app/{desktop_uidir}/` | 若 manifest 定义，目录必须存在 |\n\n### 17.3 输出\n\n打包后在当前目录生成 `{appname}.fpk` 文件。\n\n---\n\n## 18. 测试安装\n\n### 方式一：install-fpk（上传后安装）\n\n```bash\n# 将 fpk 文件上传到 fnOS 设备\nappcenter-cli install-fpk myapp.fpk\n\n# 静默安装（跳过向导）\nappcenter-cli install-fpk myapp.fpk --env config.env\n```\n\n环境变量文件 `config.env` 格式：\n```ini\n# 应用配置\nwizard_admin_username=admin\nwizard_admin_password=mypassword123\nwizard_database_type=sqlite\nwizard_app_port=8080\nwizard_agree_terms=true\n```\n\n### 方式二：install-local（开发测试）\n\n```bash\n# 在应用目录中直接安装，无需打包\ncd /path/to/myapp\nappcenter-cli install-local\n```\n\n### 检查日志\n\n日志位置：`/var/apps/{appname}/var/info.log`\n\n---\n\n## 19. 上架发布\n\n开发者后台即将上线。当前可通过加入**应用中心开发者先锋交流群**，联系专员办理应用内测和上架。\n\n---\n\n## 21. 开放 API（Open API）\n\n> 飞牛应用开放平台自 **fnOS ≥ 1.2.0401 且 fnOS App ≥ 1.34.0** 起开放了第一批系统能力，让第三方应用深度接入 fnOS：文件授权、页面路由、界面语言/主题、后端平台配置查询。前 20 章讲\"如何把应用打包成 fpk\"，本章讲\"怎样让你的 fpk 应用调用 fnOS 系统能力\"。\n>\n> 官方文档：https://developer.fnnas.com/api/overview/\n\n### 21.1 两类接口与运行环境\n\n| 接口 | 调用位置 | 用途 |\n|------|----------|------|\n| 前端 JS SDK（`@trimjs/web-app`） | 应用 Web 页面 | 打开授权页/文件选择器、读取语言主题、打开系统页面 |\n| 后端 API（Unix Socket） | 应用服务端 | 查询授权结果、检查文件权限、转换路径、读平台配置 |\n\n**接入前置**：\n1. 在 `config/resource` 中声明用到的 `api-scope`（见 §6.0）。\n2. 若使用 JS SDK，必须在 `manifest` 声明 `micro_app=true`（见 §4.1），否则页面不按微应用环境加载，JS SDK 无法初始化。\n\n**安全边界**：开放能力不绕过系统权限。应用拿到授权路径后，仍须按当前使用用户的 `uid` 校验文件权限，不能把内容直接吐给所有用户。\n\n### 21.2 前端 JS SDK 接入\n\n```bash\nnpm install @trimjs/web-app\n```\n```js\nimport { TrimApp } from '@trimjs/web-app';\nconst sdk = new TrimApp();\nconst config = await sdk.getPlatformConfig();\n```\n\n**区分运行环境**（调用文件选择/授权前必判断）：\n- `sdk.isWeb` — 是否 Web 宿主环境（移动端 App 内嵌通常为 `false`）\n- `sdk.isStandaloneWeb` — 是否独立浏览器页面（`true` 时走 `openAppAuth` 路由授权）\n\n```js\nconst authState = createAuthState();   // 应用生成的业务 state，回调时校验来源\n\nif (sdk.isStandaloneWeb) {\n  // 独立浏览器：打开系统授权页，redirectUri 收结果\n  await sdk.openAppAuth('pickUserFile', {\n    appName: 'your-app',\n    directory: true,\n    redirectUri: '/app/your-app/callback.html',\n    state: authState,\n  }, { target: '_blank', features: 'width=750,height=630' });\n} else {\n  // 宿主内：直接调用\n  await sdk.pickUserFile({ directory: true });\n}\n```\n\n> 路由授权建议结合统一网关（§13），使应用页与 `redirectUri` 回调页同源，便于 `window.opener.postMessage` 回传结果。\n\n**回调页处理**（`redirectUri` 指向的页面）：\n```js\nconst sdk = new TrimApp();\nconst result = sdk.parseAppAuthCallback(window.location.href);\n// 建议校验 result.state 确认来自本次授权请求\nif (window.opener && !window.opener.closed) {\n  window.opener.postMessage({ type: 'your-app:auth-result', result }, window.location.origin);\n}\nwindow.close();\n```\n> 移动端/平板浏览器可能不支持稳定子窗口，`window.opener` 可能为 `null`；原页面应保留\"刷新授权状态\"按钮作为兜底。`openAppAuth` 必须由用户点击触发，否则浏览器可能拦截弹窗。\n\n### 21.3 后端 API 接入\n\n所有后端 API 通过 **Unix Socket** 调用，**只能由应用服务端调用**，禁止前端直连或暴露 token：\n\n```\nPOST /api/v1/trimapp\nUnix Socket: /var/run/trim_open_gateway_apiscope.socket\nContent-Type: application/json\nAuthorization: Bearer <token>\n```\n\n`token` 由系统在调用应用脚本（如 `cmd/main`）时自动注入环境变量 **`TRIM_API_TOKEN`**，每次调用都从当前进程环境变量读取，**不要持久化或写进前端/静态文件**：\n\n```js\nconst token = process.env.TRIM_API_TOKEN;\nconst result = await request({\n  socketPath: '/var/run/trim_open_gateway_apiscope.socket',\n  path: '/api/v1/trimapp',\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },\n  body: { reqId: String(Date.now()), req: 'trim.system.getPlatformConfig', appName: 'your-app', data: {} },\n});\n```\n\n**请求结构** `{ \"reqId\": string, \"req\": string, \"appName\": string, \"data\": object }`\n**响应结构** `{ \"reqId\": string, \"code\": number, \"msg\": string, \"data\": any }`（`code` 为 `0` 表示成功）\n\n### 21.4 文件授权（核心能力）\n\n应用以独立应用用户运行，访问用户文件前需系统把目标路径 ACL 授予该应用用户。授权分两类：\n\n| 类型 | 适用 | Scope | 后端查询是否按用户 |\n|------|------|-------|--------------------|\n| **应用共享授权** | 管理员为应用授权固定目录，不按使用用户区分 | `trim.file.sharedAccess` | 不区分用户 |\n| **用户个人授权** | 按当前使用用户提供不同内容 | `trim.file.userAccess` | 需结合统一网关拿 `uid` |\n\n> 拿到授权路径 ≠ 可绕过当前用户权限。返回文件列表/预览/写入/删除前，仍应按当前用户 `uid` 调 `trim.file.checkUserACL` 校验。\n\n#### 21.4.1 应用共享授权（管理员操作，仅目录）\n```js\n// 前端：打开目录选择器（普通用户调用会失败，返回 code:1 \"仅管理员可进行此操作\"）\nconst r = await sdk.pickSharedFile({ title: '选择授权目录', sidebarGroup: ['myFiles','otherShare','favorites'] });\n// 已知目录重新申请：sdk.authorizeSharedFile('/vol1/1000/data/shared')\n```\n```json\n// 后端查询：trim.file.getSharedAccessibleFolders\n{ \"req\": \"trim.file.getSharedAccessibleFolders\", \"appName\": \"your-app\", \"data\": {} }\n// → { \"code\":0, \"data\": { \"paths\": [\"/vol1/1000/data\"] } }\n// 后端删除：trim.file.delSharedAccessibleFolder\n{ \"req\": \"trim.file.delSharedAccessibleFolder\", \"appName\": \"your-app\", \"data\": { \"path\": \"/vol1/1000/data\" } }\n```\n\n#### 21.4.2 用户个人授权（当前用户，目录或文件）\n```js\n// 目录授权（只支持单选，即使传 multiple 也按单个处理）\nconst r = await sdk.pickUserFile({ directory: true, sidebarGroup: ['myFiles','otherShare','favorites'] });\n// 文件授权（可用 accept 限制扩展名）\nconst r = await sdk.pickUserFile({ directory: false, accept: ['.jpeg','.png'] });\n// 已知路径重新申请：sdk.authorizeUserFile(path)\n```\n```json\n// 后端按用户查询目录：trim.file.getUserAccessibleFolders（不返回文件授权结果）\n{ \"req\": \"trim.file.getUserAccessibleFolders\", \"appName\": \"your-app\", \"data\": { \"uid\": 1000 } }\n// 后端删除目录：trim.file.delUserAccessibleFolder\n{ \"req\": \"trim.file.delUserAccessibleFolder\", \"appName\": \"your-app\", \"data\": { \"uid\": 1000, \"path\": \"/vol1/home/user\" } }\n```\n\n#### 21.4.3 文件权限检查 `trim.file.checkUserACL`（Scope: `trim.file.userAcl`）\n```json\n{ \"req\": \"trim.file.checkUserACL\", \"appName\": \"your-app\",\n  \"data\": { \"uid\": 1000, \"path\": [\"/vol1/1000/data/test.txt\"] } }\n// → data: [{ \"path\": \"...\", \"readable\": true, \"writable\": false, \"deletable\": false }]\n// 路径不存在/应用无权读取 → readable/writable/deletable 全 false\n```\n\n#### 21.4.4 路径转换 `trim.file.convertPath`（Scope: `trim.file.path`）\n把 `/vol1/...` 内部路径转为用户可读路径（如 `存储空间1/admin 的文件/photo`）：\n```json\n{ \"req\": \"trim.file.convertPath\", \"appName\": \"your-app\",\n  \"data\": { \"path\": [\"/vol1/1000/photo\"], \"language\": \"zh-CN\" } }\n// → data.result: [{ \"path\": \"...\", \"semanticPath\": \"存储空间1/admin 的文件/photo\" }]\n// language 必传，按当前界面语言传入\n```\n\n### 21.5 页面路由（前端 JS SDK）\n| 方法 | 作用 |\n|------|------|\n| `openFile(path)` | 用宿主打开文件 |\n| `showFileDetails(paths[])` | 打开文件详情页 |\n| `openFileManager(path)` | 打开文件管理器并定位 |\n| `openAppSetting()` | 打开本应用设置页 |\n| `openURL(url, target?, features?)` | 打开外部地址（WebView 宿主走系统浏览器） |\n\n> 打开文件/管理器/详情前，确保路径来自授权范围并按需检查用户权限。\n\n### 21.6 页面交互（前端 JS SDK）\n| 方法 | 作用 |\n|------|------|\n| `setTitle(title)` | 设置窗口标题 |\n| `$on('os/theme', cb)` | 监听主题变化（仅 Web 宿主：`isWeb && !isStandaloneWeb`） |\n| `$on('os/language', cb)` | 监听语言变化（同上限制） |\n| `setExitPageTips({title,content})` / 无参调用 | 设置 / 清除离开提示 |\n| `close()` | 关闭当前应用页 |\n\n```js\nconst config = await sdk.getPlatformConfig();      // 初始化时读语言/主题/系统版本\napplyTheme(config.theme);\nif (sdk.isWeb && !sdk.isStandaloneWeb) {\n  await sdk.$on('os/theme', (theme) => applyTheme(theme));\n}\n```\n\n### 21.7 平台配置\n- **前端**：`sdk.getPlatformConfig()` → `{ theme, language, systemLanguage, systemVersion, format:{date,time} }`\n- **后端**：`trim.system.getPlatformConfig`（Scope: `trim.system.getPlatformConfig`）→ `{ systemLanguage, systemVersion }`\n\n### 21.8 错误码\n**JSSDK**：`0` 成功；`1000000` 服务/内部异常；`1000001` 登录/认证失败；`1000002` Scope 不足；`1000030` 请求不合法/路径不支持；`1000300` 未找到应用；`1000701` 路径不存在；`1003103` 应用权限校验失败；`1003201` 管理员已关闭普通用户授权（仅管理员可操作）。普通用户调共享授权可能返回 `code:1` 或回调 `status:\"error\", error:\"access_denied\"`。\n\n**后端 API**（看 HTTP 状态码 + `code` + `msg`）：\n| HTTP | code | msg | 处理建议 |\n|------|------|-----|----------|\n| 200/400 | 200001 | Invalid Params | 检查 JSON 格式、字段类型 |\n| 401 | 200004 | Unauthorized | 检查是否拿到有效 token |\n| 403 | 200003 | Forbidden | 是否声明对应 Scope、token 是否含该 Scope |\n| 404 | 200005 | Not Found | `req` 是否写错、系统版本是否支持 |\n| 200/500 | 200006 | Internal Error | 业务模块内部错误 |\n\n---\n\n## 22. 文档更新历史\n\n| 版本 | 日期 | 主要内容 |\n|------|------|----------|\n| 20251216 | 2025-12-16 | manifest 新增 changelog；fnpack 更新至 1.0.4；新增搜索；优化创建应用教学案例（HelloFnosAppCenter） |\n| 20251231 | 2025-12-31 | 新增 New!/Update! 徽标；arch 废弃，platform 字段替代；入口配置支持环境变量；protocol 支持空字符串（自适应）；fnpack 1.2.0（新增 Linux ARM，补全校验错误处理）；新增错误异常展示处理（$TRIM_TEMP_LOGFILE）；框架文档结构调整 |\n| 20260509 | 2026-05-09 | 新增统一网关注册文档；新增登录认证文档（fnOS V1.1.3100+） |\n| 20260828 | 2026-08-28 | 新增\"21. 开放 API（Open API）\"整章（文件授权/页面路由/界面状态/后端配置/错误码）；fnpack 升级至 1.2.3；修正架构说明（新增 ARM64 支持）；manifest 新增 micro_app、config/resource 新增 api-scope、环境变量新增 TRIM_API_TOKEN |\n\n---\n\n## 附录：命令行速查\n\n```bash\n# === fnpack 打包 ===\nfnpack create <appname>                                    # 创建 Native 项目\nfnpack create <appname> --template docker                  # 创建 Docker 项目\nfnpack create <appname> --without-ui true                  # 纯服务项目\nfnpack build                                               # 打包\nfnpack build --directory <path>                            # 指定目录打包\n\n# === appcenter-cli 管理 ===\nappcenter-cli install-fpk <file.fpk>                       # 安装 fpk\nappcenter-cli install-fpk <file.fpk> --env config.env      # 静默安装\nappcenter-cli install-local                                # 本地目录安装\nappcenter-cli list                                         # 已安装列表\nappcenter-cli start <appname>                              # 启动\nappcenter-cli stop <appname>                               # 停止\nappcenter-cli manual-install [enable|disable]              # 手动安装开关\nappcenter-cli default-volume [number]                      # 默认存储空间\n```\n\nFile v1.3.1:_meta.json\n\n{\n  \"ownerId\": \"kn77pvxj7zf4hrd6q5ydgn2js5826rny\",\n  \"slug\": \"fn-fpk\",\n  \"version\": \"1.3.1\",\n  \"publishedAt\": 1787900127952\n}\n\nFile v1.3.1:skill-card.md\n\n## Description:\n\nGuides developers through building and packaging third-party Feiniu NAS fnOS FPK apps, including native and Docker apps, manifests, permissions, lifecycle scripts, UI entries, Open API integration, and testing or publishing workflows.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[dalingo81](https://clawhub.ai/user/dalingo81)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and engineers use this skill to produce fnOS FPK packaging guidance, shell commands, manifests, permission/resource configuration, lifecycle script examples, and Open API integration patterns for Feiniu NAS applications.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Unsafe shell examples could lead an agent to generate vulnerable NAS lifecycle scripts.\n\nMitigation: Review generated lifecycle scripts before use, validate or parse configuration files instead of sourcing them as shell, and avoid copying unsafe shell examples as-is.\n\nRisk: The skill may suggest installing fnpack system-wide from a downloaded binary.\n\nMitigation: Verify fnpack from an authenticated source before installation and confirm the selected binary matches the target OS and architecture.\n\nRisk: Generated packages could run with excessive privileges.\n\nMitigation: Keep apps running as the package user unless root is strictly required and justified for the specific application.\n\n## Reference(s):\n\n- [Feiniu fnOS Developer Documentation](https://developer.fnnas.com)\n- [Feiniu Open API Overview](https://developer.fnnas.com/api/overview/)\n- [fnpack 1.2.3 Download Pattern](https://static2.fnnas.com/fnpack/fnpack-1.2.3-{os}-{arch})\n\n## Skill Output:\n\n**Output Type(s):** [Guidance, Markdown, Code, Shell commands, Configuration]\n\n**Output Format:** [Markdown guidance with code blocks, configuration snippets, and shell commands]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [May include fnpack and appcenter-cli command examples plus fnOS manifest, resource, privilege, UI, wizard, and lifecycle-script snippets.]\n\n## Skill Version(s):\n\n1.3.1 (source: server release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.2.2: 3 files, 21176 bytes\n\nFiles: skill-card.md (2226b), SKILL.md (55436b), _meta.json (125b)\n\nFile v1.2.2:SKILL.md\n\n---\nname: fn-fpk\ndescription: 飞牛NAS (fnOS) FPK 应用打包开发技能。使用此技能开发和打包飞牛NAS第三方应用（.fpk），包括：Native 应用（Node.js/Python/Java/Go/Shell 等）和 Docker 应用。涵盖整个开发周期：开发环境准备、fnpack 创建项目、manifest 配置、权限/资源配置、用户入口配置（应用入口 app/ui/config + 桌面图标 + 文件右键菜单）、生命周期脚本编写（cmd/main）、向导配置（wizard）、图标规范、CGI 反向代理、统一网关注册/认证、运行时环境（Python/Node.js/Java）、中间件服务（Redis/MinIO/RabbitMQ/MariaDB）、依赖管理、fnpack CLI 打包、appcenter-cli 测试安装、到上架发布。用户提到\"飞牛\"、\"fnOS\"、\"FPK\"、\"飞牛应用\"等关键词时触发。\n---\n\n# fn-fpk — 飞牛 NAS fnOS FPK 应用开发\n\n> 基于官方文档 https://developer.fnnas.com 于 2026-06-04 逐页复审更新。\n> 系统架构: x86_64, Linux 内核 6.12.18+, Debian 发行版。\n> fnpack 版本: 1.2.1, appcenter-cli 预装在 fnOS 中。\n\n---\n\n## 1. 开发环境准备\n\n### 1.1 系统要求\n- **fnOS 版本**: ≥ 0.9.27\n- **架构**: 仅支持 x86_64 (AMD64) — 应用的编译选项也需选择 x86_64\n- **存储**: 至少创建一个存储空间\n- **权限**: 管理员权限（安装/卸载/系统设置）\n\n### 1.2 开发方式\n- **本地开发**: 在本地用 fnpack CLI 打包，再传送到 fnOS 设备安装\n- **远程开发**: SSH 到 fnOS 设备直接开发，结合 `appcenter-cli install-local` 快速测试\n\n### 1.3 技术栈\n| 语言/框架 | 支持情况 |\n|-----------|---------|\n| Node.js | ✅ (v14/v16/v18/v20/v22) |\n| Python | ✅ (3.8/3.9/3.10/3.11/3.12) |\n| Java | ✅ (11/17/21 OpenJDK) |\n| Go | ✅ Linux 运行时支持 |\n| Shell | ✅ (Bash) |\n| HTML/JS/CSS | ✅ 前端任意框架 |\n\n### 1.4 CLI 工具\n\n#### fnpack 打包工具\n下载地址：https://static2.fnnas.com/fnpack/fnpack-1.2.1-{os}-{arch}\n\n| 平台 | 二进制 |\n|------|--------|\n| Windows x86 | `fnpack-1.2.1-windows-amd64` |\n| Linux x86 | `fnpack-1.2.1-linux-amd64` |\n| Linux ARM | `fnpack-1.2.1-linux-arm64` |\n| macOS Intel | `fnpack-1.2.1-darwin-amd64` |\n| macOS M 系列 | `fnpack-1.2.1-darwin-arm64` |\n\n安装：\n```bash\nchmod +x fnpack-1.2.1-linux-amd64\nsudo mv fnpack-1.2.1-linux-amd64 /usr/local/bin/fnpack\nfnpack --help\n```\n\n#### appcenter-cli （预装在 fnOS 中）\n```bash\n# 安装 fpk 文件\nappcenter-cli install-fpk myapp.fpk\n\n# 从本地目录安装（开发模式，无需打包）\ncd /path/to/myapp\nappcenter-cli install-local\n\n# 管理应用\nappcenter-cli list              # 已安装列表\nappcenter-cli start myapp       # 启动\nappcenter-cli stop myapp        # 停止\n\n# 手动安装功能（仅测试用途）\nappcenter-cli manual-install             # 查看状态\nappcenter-cli manual-install enable      # 开启\nappcenter-cli manual-install disable     # 关闭\n\n# 设置默认存储空间\nappcenter-cli default-volume             # 查看当前\nappcenter-cli default-volume 1           # 设置存储空间1\n\n# 静默安装（跳过向导）\nappcenter-cli install-fpk myapp.fpk --env config.env\n```\n\n---\n\n## 2. 应用类型\n\n| 类型 | 描述 | 创建命令 |\n|------|------|----------|\n| **Native 应用** | 直接运行在 fnOS 上的应用 | `fnpack create <appname>` |\n| **Docker 应用** | 基于 Docker Compose 容器编排 | `fnpack create <appname> --template docker` |\n| **纯服务应用**（无 UI） | 无 Web 访问入口 | 加 `--without-ui true` |\n\n---\n\n## 3. 项目结构与核心文件\n\n### 3.1 通用结构（fnpack create 生成）\n\n```\nmyapp/\n├── app/                      # 应用可执行文件/资源目录\n│   ├── server/               # 后台服务程序（Native 应用）\n│   ├── ui/                   # Web UI 入口配置\n│   │   ├── images/           # 入口图标（icon_64.png, icon_256.png）\n│   │   └── config            # 入口配置文件（JSON）\n│   ├── www/                  # Web 静态资源（HTML/CSS/JS）\n│   └── docker/               # Docker Compose 文件（Docker 应用）\n│       └── docker-compose.yaml\n├── manifest                  # 应用基本信息（必需）\n├── cmd/                      # 生命周期管理脚本（全部必需）\n│   ├── main                  # 启动/停止/状态检查\n│   ├── install_init          # 安装前初始化\n│   ├── install_callback      # 安装后回调\n│   ├── uninstall_init        # 卸载前\n│   ├── uninstall_callback    # 卸载后\n│   ├── upgrade_init          # 升级前\n│   ├── upgrade_callback      # 升级后\n│   ├── config_init           # 配置变更前\n│   └── config_callback       # 配置变更后\n├── config/\n│   ├── privilege             # 权限配置（JSON，必需）\n│   └── resource              # 资源配置（JSON，必需）\n├── wizard/                   # 向导配置（可选）\n│   ├── install               # 安装向导\n│   ├── uninstall             # 卸载向导\n│   ├── upgrade               # 更新向导\n│   └── config                # 配置向导\n├── ICON.PNG                  # 64×64 应用图标（必需）\n├── ICON_256.PNG              # 256×256 应用图标（必需）\n└── LICENSE                   # 许可证（可选）\n```\n\n### 3.2 安装后的目录结构\n\n当应用安装到 fnOS 后，系统创建如下目录：\n\n```\n/var/apps/[appname]/\n├── cmd/                      # 生命周期脚本（来自包）\n├── config/\n│   ├── privilege             # 权限配置\n│   └── resource              # 资源配置\n├── ICON_256.PNG\n├── ICON.PNG\n├── LICENSE\n├── manifest\n├── etc -> /vol{volume}/@appconf/[appname]     # 静态配置文件\n├── home -> /vol{volume}/@apphome/[appname]    # 用户数据\n├── target -> /vol{volume}/@appcenter/[appname] # 可执行文件\n├── tmp -> /vol{volume}/@apptemp/[appname]     # 临时文件\n├── var -> /vol{volume}/@appdata/[appname]     # 运行时数据\n├── shares/                   # 共享数据目录（按 resource 配置）\n│   ├── datashare1 -> /vol{volume}/@appshare/datashare1\n│   └── datashare2 -> /vol{volume}/@appshare/datashare2\n└── wizard/                   # 向导配置（安装/卸载/升级/配置）\n    ├── install\n    ├── uninstall\n    ├── upgrade\n    └── config\n```\n\n---\n\n## 4. manifest — 应用基本信息\n\n`manifest` 文件是应用的\"身份证\"，放在项目根目录，**无扩展名**。\n\n### 4.1 完整字段参考\n\n```\n# ═══════════════ 基本信息 ═══════════════\nappname        = myapp                        # ① 应用唯一标识，系统全局唯一\nversion        = 1.0.0                        # ② 版本号：x[.y[.z]][-build]\ndisplay_name   = 我的应用                      # ③ 用户可见的名称\ndesc           = 这是一个示例应用              # ④ 详细介绍，支持 HTML 格式\nsource         = thirdparty                   # ⑤ 固定值：thirdparty\n\n# ═══════════════ 系统要求 ═══════════════\nplatform       = x86                          # ⑥ 架构：x86 | arm | all (V1.1.8+)\narch           = x86_64                       # ⑦ 已废弃，请用 platform\nos_min_version = 0.9.0                        # ⑧ 最低系统版本\nos_max_version = 0.9.100                      # ⑨ 最高系统版本\n\n# ═══════════════ 开发者信息 ═══════════════\nmaintainer     = 张三                          # 开发者/团队名称\nmaintainer_url = https://example.com           # 开发者网站\ndistributor    = 示例公司                      # 发布者\ndistributor_url = https://company.com          # 发布者网站\n\n# ═══════════════ 安装与运行控制 ═══════════════\ninstall_type   =                              # 安装位置：空=用户可选存储空间，root=系统分区\nctl_stop       = true                         # 是否显示启动/停止按钮，默认 true\ncheckport      = true                         # 是否启用端口检查，默认 true\nservice_port   = 8080                         # 应用监听端口（单个端口）\ndisable_authorization_path = false            # 是否禁用授权目录功能\n\n# ═══════════════ 用户界面 ═══════════════\ndesktop_uidir          = ui                   # UI 组件目录（相对应用根目录）\ndesktop_applaunchname  = myapp.Application    # 默认启动入口 ID\n\n# ═══════════════ 依赖管理 ═══════════════\ninstall_dep_apps = mariaDB:redis              # 依赖应用列表，格式：app1>2.2.2:app2:app3\n\n# ═══════════════ 应用更新 ═══════════════\nchangelog = 新增了XX功能                       # 更新日志（升级时展示）\n```\n\n### 4.2 字段详解\n\n#### 应用标识\n| 字段 | 必填 | 说明 |\n|------|------|------|\n| `appname` | ✅ | 全局唯一标识符，用于系统识别 |\n| `version` | ✅ | 格式：`x[.y[.z]][-build]`，例如 `1.0.0`、`2.1.3-beta` |\n| `display_name` | ✅ | 应用中心显示的名称 |\n| `desc` | ✅ | 详细介绍，支持 HTML 格式 |\n| `source` | ✅ | 固定为 `thirdparty` |\n\n#### 系统要求（V1.1.8+ 新增 platform 字段）\n| 字段 | 说明 |\n|------|------|\n| `platform = x86` | 仅支持 x86 架构 |\n| `platform = arm` | 仅支持 arm 架构 |\n| `platform = all` | 所有架构，Docker 应用常用 |\n| `arch` | 【已废弃】用 platform 替代 |\n\n#### 安装控制\n| 字段 | 默认值 | 说明 |\n|------|--------|------|\n| `install_type` | 空 | `root` = 安装到系统分区 `/usr/local/apps/@appcenter/`；空 = 用户选择存储位置 |\n| `ctl_stop` | `true` | `false` 时隐藏启动/停止按钮和运行状态（无进程应用） |\n| `checkport` | `true` | `false` 时系统不检查端口占用 |\n| `service_port` | — | 应用监听端口（仅支持单个端口） |\n| `disable_authorization_path` | `false` | `true` 时应用设置页不显示授权目录操作 |\n\n#### 依赖管理\n```ini\n# 格式：app1>2.2.2:app2:app3\n# > 表示最低版本要求\n# : 分隔多个依赖\n# 系统按列表顺序自动安装依赖\ninstall_dep_apps = mariaDB:redis\n```\n\n#### 官方格式参考\n\n官方文档中 manifest 采用**等号紧贴**格式（等号前后无空格），两种格式均可正常工作：\n\n```ini\nappname=myapp\nversion=1.0.0\ndisplay_name=我的应用\ndesc=这是一个示例应用，展示了 manifest 文件的基本用法\narch=x86_64\nsource=thirdparty\n\nmaintainer=张三\nmaintainer_url=https://example.com\ndistributor=示例公司\ndistributor_url=https://company.com\n\nos_min_version=0.9.0\n\ndesktop_uidir=ui\ndesktop_applaunchname=myapp.APPLICATION\n\nservice_port=8080\ncheckport=true\n\ninstall_dep_apps=mariaDB:redis\n```\n\n---\n\n## 5. 权限配置 (config/privilege)\n\n`config/privilege` 文件定义应用运行时的权限级别和用户身份，JSON 格式，**必需**。\n\n### 5.1 默认权限模式（推荐）\n\n```json\n{\n  \"defaults\": {\n    \"run-as\": \"package\"\n  },\n  \"username\": \"myapp_user\",\n  \"groupname\": \"myapp_group\"\n}\n```\n\n| 字段 | 默认值 | 说明 |\n|------|--------|------|\n| `run-as` | `package` | `package` = 应用用户模式，`root` = root 模式 |\n| `username` | appname | 应用专用用户名 |\n| `groupname` | appname | 应用专用用户组名 |\n\n#### 默认模式行为\n- 系统为应用创建专用用户和用户组\n- 所有应用进程以专用用户身份运行\n- 应用文件所有者是该专用用户\n- 应用只能访问自己的目录和系统允许的公共资源\n\n### 5.2 Root 权限模式\n\n> ⚠️ 仅飞牛官方合作的企业开发者可使用，第三方应用默认无法在应用中心发布 root 权限应用。\n\n```json\n{\n  \"defaults\": {\n    \"run-as\": \"root\"\n  },\n  \"username\": \"myapp_user\",\n  \"groupname\": \"myapp_group\"\n}\n```\n\n#### Root 模式行为\n- 应用脚本以 root 身份执行\n- 应用进程可以 root 身份或指定应用用户身份运行\n- 应用文件所有者变为 root\n- 系统仍会创建应用专用用户和用户组\n\n### 5.3 外部文件访问权限\n\n应用默认无法访问用户个人文件。用户需要在应用设置中授权：\n- **读写权限**：读取和修改文件\n- **只读权限**：只能读取，不能修改\n- **禁止访问**：无法访问该路径\n\n也可通过 `config/resource` 的 `data-share` 设置默认共享目录。\n\n### 5.4 当前用户检查\n\n```bash\necho \"当前运行用户: $TRIM_RUN_USERNAME\"\necho \"应用专用用户: $TRIM_USERNAME\"\n```\n\n---\n\n## 6. 资源配置 (config/resource)\n\n`config/resource` 文件声明应用的扩展能力，JSON 格式，**必需**。\n\n### 6.1 数据共享 (data-share)\n\n创建共享目录，用户通过 **文件管理 → 应用文件** 可访问，应用也可实时访问。\n\n```json\n{\n  \"data-share\": {\n    \"shares\": [\n      {\n        \"name\": \"documents\",\n        \"permission\": {\n          \"rw\": [\"myapp_user\"]\n        }\n      },\n      {\n        \"name\": \"documents/backups\",\n        \"permission\": {\n          \"ro\": [\"myapp_user\"]\n        }\n      }\n    ]\n  }\n}\n```\n\n| 字段 | 说明 |\n|------|------|\n| `name` | 共享目录名，支持多级（如 `documents/backups`） |\n| `permission.rw` | 读写权限用户列表 |\n| `permission.ro` | 只读权限用户列表 |\n\n### 6.2 系统集成 (usr-local-linker)\n\n应用启动时自动创建软链接到系统目录，停止时自动移除。\n\n```json\n{\n  \"usr-local-linker\": {\n    \"bin\": [\n      \"bin/myapp-cli\",\n      \"bin/myapp-server\"\n    ],\n    \"lib\": [\n      \"lib/mylib.so\",\n      \"lib/mylib.a\"\n    ],\n    \"etc\": [\n      \"etc/myapp.conf\",\n      \"etc/myapp.d/default.conf\"\n    ]\n  }\n}\n```\n\n| 链接 | 目标目录 |\n|------|----------|\n| `bin` | `/usr/local/bin/` |\n| `lib` | `/usr/local/lib/` |\n| `etc` | `/usr/local/etc/` |\n\n### 6.3 Docker 项目支持 (docker-project)\n\nDocker 应用需要在 `config/resource` 中声明：\n\n```json\n{\n  \"docker-project\": {\n    \"projects\": [\n      {\n        \"name\": \"myapp-stack\",\n        \"path\": \"docker\"\n      }\n    ]\n  }\n}\n```\n\n| 字段 | 说明 |\n|------|------|\n| `name` | Docker Compose 项目名称 |\n| `path` | 相对于 app 目录的路径，指向含 `docker-compose.yaml` 的文件夹 |\n\n---\n\n## 7. 入口配置 (app/ui/config)\n\n应用入口是用户访问应用的\"大门\"。配置文件位于 `app/ui/config`（JSON 格式），入口键名必须以 `appname` 为前缀。\n\n### 7.1 桌面图标入口\n\n```json\n{\n  \".url\": {\n    \"myapp.main\": {\n      \"title\": \"我的应用\",\n      \"icon\": \"images/icon_{0}.png\",\n      \"type\": \"url\",\n      \"protocol\": \"http\",\n      \"port\": \"8080\",\n      \"url\": \"/\",\n      \"allUsers\": true\n    },\n    \"myapp.admin\": {\n      \"title\": \"管理后台\",\n      \"icon\": \"images/admin_icon_{0}.png\",\n      \"type\": \"url\",\n      \"protocol\": \"http\",\n      \"port\": \"8080\",\n      \"url\": \"/admin\",\n      \"allUsers\": false\n    }\n  }\n}\n```\n\n### 7.2 文件右键入口\n\n```json\n{\n  \".url\": {\n    \"myapp.editor\": {\n      \"title\": \"文本编辑器\",\n      \"icon\": \"images/editor-{0}.png\",\n      \"type\": \"url\",\n      \"protocol\": \"http\",\n      \"port\": \"8080\",\n      \"url\": \"/edit\",\n      \"allUsers\": true,\n      \"fileTypes\": [\"txt\", \"md\", \"json\", \"xml\"],\n      \"noDisplay\": true\n    }\n  }\n}\n```\n\n`fileTypes` + `noDisplay: true` 实现\"只在文件右键菜单中显示，不在桌面显示\"。\n\n### 7.3 CGI 入口（推荐用于静态页面/Native 应用）\n\n```json\n{\n  \".url\": {\n    \"myapp.Application\": {\n      \"title\": \"我的应用\",\n      \"icon\": \"images/icon_{0}.png\",\n      \"type\": \"iframe\",\n      \"protocol\": \"http\",\n      \"url\": \"/cgi/ThirdParty/myapp/index.cgi/\",\n      \"allUsers\": true\n    }\n  }\n}\n```\n\n> CGI 方案不需要声明 `port` 字段。\n\n### 7.4 入口字段参考\n\n| 字段 | 必填 | 说明 |\n|------|------|------|\n| `title` | ✅ | 入口显示标题（桌面图标名称/右键菜单名称） |\n| `icon` | ✅ | 图标路径（相对 UI 目录），`{0}` 会被替换为 `64` 或 `256` |\n| `type` | ✅ | `url` = 新标签页打开，`iframe` = 桌面窗口内嵌 |\n| `protocol` | ✅ | `http` / `https` / `\"\"`（空字符串=自适应协议；不声明则默认 `http`） |\n| `port` | ✅ | 端口号；CGI 方案无需声明；可使用 `${wizard_port}` 动态配置 (V1.1.8+) |\n| `url` | ✅ | 访问路径（相对路径）；可使用 `${wizard_url}` 动态配置 (V1.1.8+) |\n| `allUsers` | ✅ | `true` = 所有用户可见，`false` = 仅管理员可见 |\n| `fileTypes` | ❌ | 文件右键关联的文件扩展名数组，如 `[\"txt\",\"md\"]` |\n| `noDisplay` | ❌ | `true` = 不在桌面显示，仅右键菜单显示 |\n| `accessPerm` | ❌ | 桌面访问设置权限：`editable` / `readonly` / `hidden` |\n| `gatewaySocket` | ❌ | 统一网关 Socket 文件名 |\n| `gatewayPrefix` | ❌ | 统一网关访问前缀，如 `/app/myapp` |\n\n#### 入口配置文件支持环境变量 (V1.1.8+)\n```json\n{\n  \"myapp.configurable\": {\n    \"title\": \"可配置应用\",\n    \"port\": \"${wizard_port}\",\n    \"url\": \"${wizard_path}\"\n  }\n}\n```\n\n#### 控制字段\n\n控制字段用于管理入口在**应用中心 → 应用设置**中对用户的显示和编辑权限。\n\n| 字段 | 值 | 说明 |\n|------|-----|------|\n| `accessPerm` | `editable` / `readonly` / `hidden` | 桌面访问的设置权限，默认为 `readonly` |\n\n```json\n{\n  \"myapp.advanced\": {\n    \"title\": \"高级功能\",\n    \"icon\": \"images/advanced-{0}.png\",\n    \"type\": \"iframe\",\n    \"protocol\": \"http\",\n    \"port\": \"8080\",\n    \"url\": \"/advanced\",\n    \"allUsers\": false,\n    \"control\": {\n      \"accessPerm\": \"readonly\"\n    },\n    \"fileTypes\": [\"pdf\", \"doc\", \"docx\"],\n    \"noDisplay\": true\n  }\n}\n```\n\n> 注意：`portPerm` 和 `pathPerm` 控制字段在 V1.1.8 版本及以上已废弃。\n\n### 7.5 文件路径参数\n\n通过右键菜单打开文件时，系统自动在 URL 后拼接 `path` 参数：\n```\nhttp://localhost:8080/edit?path=/vol1/Users/admin/Documents/example.txt\n```\n\n### 7.6 CGI 脚本示例\n\n`app/ui/index.cgi` — 通过 Shell 脚本实现静态资源转发：\n\n```bash\n#!/bin/bash\n\n# 【注意】修改为你的静态文件根目录\nBASE_PATH=\"/var/apps/App.Native.HelloFnosAppCenter/target/www\"\n\n# 从 REQUEST_URI 里拿到 index.cgi 后面的路径\nURI_NO_QUERY=\"${REQUEST_URI%%\\?*}\"\nREL_PATH=\"/\"\n\ncase \"$URI_NO_QUERY\" in\n  *index.cgi*)\n    REL_PATH=\"${URI_NO_QUERY#*index.cgi}\"\n    ;;\nesac\n\nif [ -z \"$REL_PATH\" ] || [ \"$REL_PATH\" = \"/\" ]; then\n  REL_PATH=\"/index.html\"\nfi\n\nTARGET_FILE=\"${BASE_PATH}${REL_PATH}\"\n\n# 防御 .. 越级访问\nif echo \"$TARGET_FILE\" | grep -q '\\.\\.'; then\n  echo \"Status: 400 Bad Request\"\n  echo \"Content-Type: text/plain; charset=utf-8\"\n  echo \"\"\n  echo \"Bad Request\"\n  exit 0\nfi\n\nif [ ! -f \"$TARGET_FILE\" ]; then\n  echo \"Status: 404 Not Found\"\n  echo \"Content-Type: text/plain; charset=utf-8\"\n  echo \"\"\n  echo \"404 Not Found: ${REL_PATH}\"\n  exit 0\nfi\n\n# 根据扩展名判断 Content-Type（精简版，完整版见社区参考）\next=\"${TARGET_FILE##*.}\"\ncase \"$ext\" in\n  html|htm)  mime=\"text/html; charset=utf-8\" ;;\n  css)       mime=\"text/css; charset=utf-8\" ;;\n  js)        mime=\"application/javascript; charset=utf-8\" ;;\n  png)       mime=\"image/png\" ;;\n  jpg|jpeg)  mime=\"image/jpeg\" ;;\n  *)         mime=\"application/octet-stream\" ;;\nesac\n\necho \"Content-Type: $mime\"\necho \"\"\ncat \"$TARGET_FILE\"\n```\n\n> 更多 CGI 实现参考（社区共建团 FNOSP 开源）：https://github.com/FNOSP/fnosAppCenterCgiCollection\n> 该仓库收录了 Node.js、Python、Shell 等多种语言的 CGI 实现案例。\n\n---\n\n## 8. 生命周期脚本 (cmd/)\n\n系统通过调用 `cmd/` 目录下的脚本来管理应用的全生命周期。所有脚本均为 Bash 脚本，全部**必需**（即使只写 `exit 0`）。\n\n> 注意：不要用 `echo` 直接输出错误信息，而是写入 `$TRIM_TEMP_LOGFILE`；不要直接 `exit`，而是返回错误码 `1`。\n\n### 8.1 cmd/main — 启动/停止/状态检查\n\n```bash\n#!/bin/bash\n\ncase $1 in\nstart)\n  # 启动应用的命令，成功返回 0，失败返回 1\n  exit 0\n  ;;\nstop)\n  # 停止应用的命令，成功返回 0，失败返回 1\n  exit 0\n  ;;\nstatus)\n  # 检查应用运行状态，运行中返回 0，未运行返回 3\n  exit 0\n  ;;\n*)\n  exit 1\n  ;;\nesac\n```\n\n#### 应用状态监控\n\n| 返回值 | 含义 |\n|--------|------|\n| `exit 0` | 应用正在运行 |\n| `exit 3` | 应用未运行 |\n| `exit 1` | 执行失败 |\n\n系统会在启动前检查一次，运行期间定期轮询检查。\n\n### 8.2 Native 应用完整启动脚本\n\n```bash\n#!/bin/bash\n\nLOG_FILE=\"${TRIM_PKGVAR}/info.log\"\nPID_FILE=\"${TRIM_PKGVAR}/app.pid\"\nDATA_DIR=\"${TRIM_DATA_SHARE_PATHS%%:*}\"\nCMD=\"DATA_DIR=${DATA_DIR} PORT=5001 node ${TRIM_APPDEST}/server/server.js\"\n\nlog_msg() {\n  echo \"$(date '+%Y-%m-%d %H:%M:%S') - $1\" >> ${LOG_FILE}\n}\n\nstart_process() {\n  if status; then return 0; fi\n  log_msg \"Starting process ...\"\n  bash -c \"${CMD}\" >> ${LOG_FILE} 2>&1 &\n  printf \"%s\" \"$!\" > ${PID_FILE}\n  return 0\n}\n\nstop_process() {\n  log_msg \"Stopping process ...\"\n  if [ -r \"${PID_FILE}\" ]; then\n    pid=$(head -n 1 \"${PID_FILE}\" | tr -d '[:space:]')\n    log_msg \"pid=${pid}\"\n    if ! kill -0 \"${pid}\" 2>/dev/null; then\n      rm -f \"${PID_FILE}\"\n      return\n    fi\n    log_msg \"send TERM signal to PID:${pid}...\"\n    kill -TERM ${pid} >> ${LOG_FILE} 2>&1\n    local count=0\n    while kill -0 \"${pid}\" 2>/dev/null && [ $count -lt 10 ]; do\n      sleep 1; count=$((count + 1))\n    done\n    if kill -0 \"${pid}\" 2>/dev/null; then\n      kill -KILL \"${pid}\"\n    fi\n    rm -f \"${PID_FILE}\"\n  fi\n}\n\nstatus() {\n  if [ -f \"${PID_FILE}\" ]; then\n    pid=$(head -n 1 \"${PID_FILE}\" | tr -d '[:space:]')\n    if kill -0 \"${pid}\" 2>/dev/null; then return 0; fi\n    rm -f \"${PID_FILE}\"\n  fi\n  return 1\n}\n\ncase $1 in\n  start)  start_process ;;\n  stop)   stop_process ;;\n  status) if status; then exit 0; else exit 3; fi ;;\n  *)      exit 1 ;;\nesac\n```\n\n### 8.3 运行环境配置\n\n#### Node.js\n```bash\n# 在 cmd 脚本中配置，可选版本：nodejs_v22 / v20 / v18 / v16 / v14\nexport PATH=/var/apps/nodejs_v22/target/bin:$PATH\nnode -v\nnpm -v\n```\n\n#### Python\n```bash\n# 可选版本：python312 / 311 / 310 / 39 / 38\nexport PATH=/var/apps/python312/target/bin:$PATH\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\n```\n\n#### Java\n```bash\n# 可选版本：java-21-openjdk / java-17-openjdk / java-11-openjdk\nexport PATH=/var/apps/java-21-openjdk/target/bin:$PATH\njava --version\n```\n\n### 8.4 应用配置与文件写入\n\n飞牛 NAS 应用安装后，系统会在多个目录下为应用分配存储空间。不同目录的用途和权限差异很大，理解它们对写出健壮的应用至关重要。\n\n#### 目录一览\n\n| 目录 | 权限 | 用途 | 系统写入 | 应用写入 |\n|------|------|------|----------|----------|\n| `/vol1/@appconf/{appname}/settings.conf` | 应用用户 | **应用配置**（端口、路径等） | ✅（向导/设置页面） | ✅ |\n| `/vol1/@appdata/{appname}/` | 应用用户 | **应用数据**（运行时日志、数据库等） | ✘ | ✅ |\n| `/vol1/@apphome/{appname}/` | 应用用户 | **应用家目录** | ✘ | ✅ |\n| `/vol1/@apptemp/{appname}/` | 应用用户 | **缓存/临时文件** | ✘ | ✅ |\n| `/vol1/@appshare/{appname}/` | 应用用户 | **共享目录**（同 `TRIM_DATA_SHARE_PATHS`） | ✘ | ✅ |\n| `/vol1/@appcenter/{appname}/` | 应用用户 | **应用本身代码（只读）** | ✘（可手动修改但不建议） | ✘ |\n\n> 存储在 `/vol1/@appcenter/` 下的是应用包的运行目录（`app/server/` 等），不是 `@appconf/`。\n\n#### settings.conf：应用配置的核心\n\n`/vol1/@appconf/{appname}/settings.conf` 是 NAS 系统与应用之间的配置桥梁。它的格式为 `KEY=VALUE` 纯文本，**系统会在以下时机自动重写此文件**：\n\n1. **安装/更新**时——写入 `wizard/` 向导收集的用户输入（以 `wizard_` 为前缀的环境变量）\n2. **应用设置页面**修改时——写入授权文件夹、端口等系统级配置\n3. 应用本身也可以主动写入此文件，但要注意**系统下次重写时会覆盖**\n\n#### ⚠️ 常见陷阱：系统重写覆盖问题\n\n```\n# settings.conf 被系统重写后：\nMUSIC_PATH=/vol1/1000/Music       ← 路径被系统改写，可能与真实路径名不符\nPORT=5200\n```\n\n已知问题：\n1. **路径命名差异**——用户实际选的文件夹名（如 `music11` / `music22`）被系统写成了 `Music`（大小写不一致或截断）\n2. **授权文件夹丢失**——用户授权了多个数据目录，但系统只保存了第一个\n3. **每次修改设置都会覆盖**——手动改 `settings.conf` 后，只要用户进一次应用设置页面就会被重置\n4. **`data-share` 可能为空**——`config/resource` 中 `data-share.shares` 数组不更新，导致 `TRIM_DATA_ACCESSIBLE_PATHS` 环境变量为空\n\n#### 应对策略\n\n**策略一：应用启动时自读 settings.conf（推荐，最通用）**\n\n不依赖 `cmd/main` 传环境变量，让应用服务进程启动时**自己读取 `settings.conf`**。这样不管 NAS 系统以何种方式拉起进程（自动启动、手动重启）都能正确拿到配置。\n\n```javascript\n// Node.js — 在读取 PORT / MUSIC_PATH 之前执行\nvar path = require('path');\nvar fs = require('fs');\n\n// settings.conf 实际位置：\n//   @appconf 的挂载点 = __dirname 减去 @appcenter 后的相对路径\nvar confPath = path.resolve(__dirname, '../../../@appconf/YOUR_APP_NAME/settings.conf');\ntry {\n  var confContent = fs.readFileSync(confPath, 'utf-8');\n  var confLines = confContent.split(String.fromCharCode(10));\n  for (var i = 0; i < confLines.length; i++) {\n    var line = confLines[i].trim();\n    if (line && !line.startsWith('#')) {\n      var eqIdx = line.indexOf('=');\n      if (eqIdx > 0) {\n        var key = line.substring(0, eqIdx).trim();\n        var val = line.substring(eqIdx + 1).trim();\n        if (process.env[key] === undefined) process.env[key] = val;\n      }\n    }\n  }\n} catch(e) { /* 配置文件不存在时忽略 */ }\n\nconst PORT = process.env.PORT || 6688;      // 已被 settings.conf 注入\n```\n\n> 注意：这段代码必须放在 `PORT` 声明**之前**，否则环境变量已经读过了，兜底逻辑不会生效。\n\n```python\n# Python 版本\nimport os\n\ndef load_settings():\n    config_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))),\n                               '..', '@appconf', 'YOUR_APP_NAME', 'settings.conf')\n    # 更准确的：从 __file__ 向上走到 @appcenter，再到 @appconf\n    config_path = os.path.normpath(config_path)\n    if not os.path.exists(config_path):\n        return\n    with open(config_path, 'r') as f:\n        for line in f:\n            line = line.strip()\n            if not line or line.startswith('#'):\n                continue\n            if '=' in line:\n                key, val = line.split('=', 1)\n                key = key.strip()\n                val = val.strip()\n                if key not in os.environ:\n                    os.environ[key] = val\n\nload_settings()\nPORT = int(os.environ.get('PORT', 6688))\n```\n\n**策略二：只用 cmd/main 传环境变量（较脆弱）**\n\n```bash\n# cmd/main 中正确的做法：读 settings.conf + 传环境变量\n. /vol1/@appconf/YOUR_APP_NAME/settings.conf\nexport PORT MUSIC_PATH\nnode /vol1/@appcenter/YOUR_APP_NAME/server/server.js\n```\n\n局限：NAS 自动启动时可能不经过 `cmd/main`，环境变量仍然丢失。\n\n**策略三：验证路径有效性，失败时友好报错**\n\n```javascript\nvar musicPaths = (process.env.MUSIC_PATH || '').split(':').filter(Boolean);\nvar validPaths = [];\nmusicPaths.forEach(function(p) {\n  try {\n    if (fs.statSync(p).isDirectory()) {\n      validPaths.push(p);\n    }\n  } catch(e) {\n    console.error('Music directory not found:', p);\n  }\n});\nif (validPaths.length === 0) {\n  console.error('No valid music folder. Please set it in App Settings.');\n  process.exit(1);\n}\n```\n\n#### 实践建议\n\n1. **首选策略一**（应用自读 settings.conf）+ **策略三**（路径验证），覆盖所有启动场景\n2. 不要依赖 `TRIM_DATA_ACCESSIBLE_PATHS` 环境变量——它可能为空\n3. 对于 Native 应用，建议在 `cmd/main` 和 app 代码中都实现配置读取，双重保险\n4. 如果发现路径不匹配（如实际文件夹 `music11` 但配置写 `Music`），先确认是否是 NAS 系统写入的问题，**不要**在代码里做硬编码的路径猜测\n\n#### ⚠️ 端口变更的实战陷阱\n\n##### 问题描述\n用户在安装向导/应用设置中修改端口（`wizard_port`）后，应用重启仍然监听默认端口。\n\n##### 根因分析\n飞牛系统通过 `TRIM_SERVICE_PORT` 环境变量（来源：manifest 的 `service_port` 字段）将端口注入 `cmd/main`。但关键问题在于：\n\n- **`service_port` 只在打包时固定**，用户在安装向导或应用设置中修改端口后，`service_port` **不会自动更新**\n- `TRIM_SERVICE_PORT` 的值在首次安装后即固定，不会因 `wizard_port` 变更而重新注入\n- `config_callback` 虽然能通过 `$wizard_port` 变量拿到新端口，但直接调用 `cmd/main start` 时传给 Node 的仍然是旧的 `TRIM_SERVICE_PORT`\n\n##### 修复方案：三级端口优先级\n`cmd/main` 中端口获取应按以下优先级：\n\n```bash\n# 第一优先级：settings.conf 中持久化的 PORT（来自安装向导 / 应用设置的 wizard_port）\nif [ -f \"${TRIM_PKGETC}/settings.conf\" ]; then\n  . \"${TRIM_PKGETC}/settings.conf\"\nfi\n# 第二优先级：TRIM_SERVICE_PORT（系统注入，来自 manifest service_port）\n# 第三优先级：默认值\nPORT=\"${PORT:-${TRIM_SERVICE_PORT:-8080}}\"\n```\n\n##### 完整工作流\n1. **安装时**：`install_callback` 将 `$wizard_port` 写入 `${TRIM_PKGETC}/settings.conf`\n2. **修改端口时**（应用设置页面）：`config_callback` 将 `$wizard_port` 写入 `settings.conf`，然后重启\n3. **任何后续启动**（系统自动启动、手动重启）：`cmd/main` 优先读取 `settings.conf` 中的 `PORT`，不受 `TRIM_SERVICE_PORT` 旧值影响\n\n##### 配套的 config_callback\n```bash\nPORT=\"${wizard_port:-8080}\"\n\n# 先持久化端口到 settings.conf\nmkdir -p \"${TRIM_PKGETC}\"\ncat > \"${TRIM_PKGETC}/settings.conf\" <<EOF\nPORT=${PORT}\nEOF\n\n# 然后重启（此时不需要传 TRIM_SERVICE_PORT，因为 cmd/main 会自读 settings.conf）\nbash \"${0%/*}/main\" stop\nsleep 1\nbash \"${0%/*}/main\" start\n```\n\n##### 要点\n- **`settings.conf` 的自定义写入要放到 `install_callback` 和 `config_callback` 中**，不要在 `install_init` 或其他脚本中写，否则可能在安装流程中被系统覆盖\n- `TRIM_SERVICE_PORT` 仍然可以作为 fallback 使用，但**不要把它作为唯一的端口来源**\n- 如果 `settings.conf` 被系统重写覆盖了 `PORT`（某些系统版本已知问题），检查端口是否仍然正确，必要时在 `config_init` 中添加校验\n\n### 8.4 Docker 应用的 main 脚本\n\nDocker 应用的启停由系统通过 compose 管理，但需要定义状态检查：\n\n```bash\n#!/bin/bash\n\nFILE_PATH=\"${TRIM_APPDEST}/docker/docker-compose.yaml\"\n\nis_docker_running () {\n  DOCKER_NAME=\"\"\n  if [ -f \"$FILE_PATH\" ]; then\n    DOCKER_NAME=$(cat $FILE_PATH | grep \"container_name\" | awk -F ':' '{print $2}' | xargs)\n    echo \"DOCKER_NAME is set to: $DOCKER_NAME\"\n  fi\n  if [ -n \"$DOCKER_NAME\" ]; then\n    docker inspect $DOCKER_NAME | grep -q \"\\\"Status\\\": \\\"running\\\",\" || exit 1\n    return\n  fi\n}\n\ncase $1 in\n  start)  exit 0 ;;  # compose 管理，无需额外操作\n  stop)   exit 0 ;;  # compose 管理，无需额外操作\n  status) if is_docker_running; then exit 0; else exit 3; fi ;;\n  *)      exit 1 ;;\nesac\n```\n\n### 8.5 错误异常展示处理（V1.1.8+）\n\n向 `$TRIM_TEMP_LOGFILE` 写入错误信息并返回错误码 `1`，系统会自动以 Dialog 对话框展示给用户：\n\n```bash\n# ✅ 正确做法\necho \"配置文件不存在，应用启动失败！\" > \"${TRIM_TEMP_LOGFILE}\"\nexit 1\n\n# ❌ 错误做法\n# echo \"配置文件不存在，应用启动失败！\"\n# exit 1\n```\n\n### 8.6 生命周期事件说明\n\n| 事件 | 脚本 | 时机 |\n|------|------|------|\n| 安装前 | `install_init` | 文件解压前，可做环境检查、依赖安装 |\n| 安装后 | `install_callback` | 文件解压后，可做初始化配置 |\n| 卸载前 | `uninstall_init` | 停止应用后，卸载前 |\n| 卸载后 | `uninstall_callback` | 可清理剩余数据和目录 |\n| 升级前 | `upgrade_init` | 更新处理前 |\n| 升级后 | `upgrade_callback` | 可做数据库升级、配置迁移 |\n| 配置变更前 | `config_init` | 用户保存配置后，重启前 |\n| 配置变更后 | `config_callback` | 可监听配置变化调整运行逻辑 |\n\n#### 卸载时保留/删除数据\n系统默认保留 `var` 和 `shares` 目录。如果希望在卸载向导中让用户选择是否删除数据，在 `wizard/uninstall` 中配置选项，在 `cmd/uninstall_callback` 中根据用户选择清理：\n\n```bash\nif [ \"$wizard_data_action\" = \"delete\" ]; then\n  rm -rf \"${TRIM_PKGVAR}\" \"${TRIM_DATA_SHARE_PATHS}\"\nfi\n```\n\n---\n\n## 9. 环境变量参考\n\n### 9.1 系统环境变量\n\n| 变量 | 说明 |\n|------|------|\n| `$TRIM_APPNAME` | 应用名称 |\n| `$TRIM_APPVER` | 应用版本号 |\n| `$TRIM_APPDEST` | 应用可执行文件目录（target） |\n| `$TRIM_PKGETC` | 配置文件目录（etc） |\n| `$TRIM_PKGVAR` | 运行时数据目录（var） |\n| `$TRIM_TEMP_LOGFILE` | 临时日志文件路径（用户可见） |\n| `$TRIM_SERVICE_PORT` | 服务端口 |\n| `$TRIM_USERNAME` | 应用专用用户名 |\n| `$TRIM_RUN_USERNAME` | 当前运行用户（`root` 或应用用户） |\n| `$TRIM_DATA_SHARE_PATHS` | 数据共享目录路径列表（冒号分隔） |\n\n#### 系统环境变量使用示例（官方文档参考）\n\n`cmd/main` 中典型用法如下：\n\n```bash\ncase $1 in\nstart)\n  echo \"启动应用: $TRIM_APPNAME 版本: $TRIM_APPVER\"\n  echo \"应用目录: $TRIM_APPDEST\"\n  echo \"配置文件目录: $TRIM_PKGETC\"\n  echo \"数据目录: $TRIM_PKGVAR\"\n  echo \"服务端口: $TRIM_SERVICE_PORT\"\n\n  # 检查配置文件是否存在\n  if [ ! -f \"$TRIM_PKGETC/config.conf\" ]; then\n    echo \"配置文件不存在，创建默认配置...\"\n    cp \"$TRIM_APPDEST/config.conf.example\" \"$TRIM_PKGETC/config.conf\"\n  fi\n\n  # 启动应用\n  cd \"$TRIM_APPDEST\"\n  ./myapp --config \"$TRIM_PKGETC/config.conf\" \\\n    --data \"$TRIM_PKGVAR\" \\\n    --port \"$TRIM_SERVICE_PORT\" \\\n    --user \"$TRIM_USERNAME\" \\\n    --log \"$TRIM_TEMP_LOGFILE\" &\n\n  echo \"应用启动完成\"\n  exit 0\n  ;;\n\nstatus)\n  if pgrep -f \"myapp.*$TRIM_SERVICE_PORT\" > /dev/null; then\n    echo \"应用正在运行\"\n    exit 0\n  else\n    echo \"应用未运行\"\n    exit 3\n  fi\n  ;;\n\nstop)\n  echo \"停止应用...\"\n  pkill -f \"myapp.*$TRIM_SERVICE_PORT\"\n  exit 0\n  ;;\n\n*)\n  echo \"未知命令: $1\"\n  exit 1\n  ;;\nesac\n```\n\n### 9.5 向导输入变量\n\n用户在向导中的选择会变成同名的环境变量，在脚本中可直接使用：\n\n```bash\nADMIN_USERNAME=\"$wizard_admin_username\"\nDATABASE_TYPE=\"$wizard_database_type\"\nAPP_PORT=\"$wizard_app_port\"\n```\n\n### 9.6 应用入口中的动态变量（V1.1.8+）\n\n入口配置文件 `app/ui/config` 中可使用 `${variable_name}` 动态引用向导参数：\n\n```json\n{\n  \"port\": \"${wizard_port}\",\n  \"url\": \"${wizard_path}\"\n}\n```\n\n---\n\n## 10. 向导配置 (wizard/)\n\n向导是用户与应用交互的\"引导员\"，JSON 格式，每个文件是一个 JSON 数组（多个步骤）。\n\n### 10.1 向导类型\n\n| 文件 | 用途 |\n|------|------|\n| `wizard/install` | 安装时的配置界面 |\n| `wizard/uninstall` | 卸载时的确认界面 |\n| `wizard/upgrade` | 更新时的配置界面 |\n| `wizard/config` | 设置时的配置界面 |\n\n### 10.2 向导文件结构\n\n```json\n[\n  {\n    \"stepTitle\": \"第一步标题\",\n    \"items\": [\n      { 表单项 1 },\n      { 表单项 2 }\n    ]\n  },\n  {\n    \"stepTitle\": \"第二步标题\",\n    \"items\": [\n      { 表单项 3 }\n    ]\n  }\n]\n```\n\n### 10.3 表单项类型\n\n#### text — 文本输入\n```json\n{\n  \"type\": \"text\",\n  \"field\": \"wizard_username\",\n  \"label\": \"用户名\",\n  \"initValue\": \"admin\",\n  \"rules\": [\n    { \"required\": true, \"message\": \"请输入用户名\" },\n    { \"min\": 3, \"max\": 20, \"message\": \"长度应在3-20字符之间\" }\n  ]\n}\n```\n\n#### password — 密码输入\n```json\n{\n  \"type\": \"password\",\n  \"field\": \"wizard_password\",\n  \"label\": \"管理员密码\",\n  \"rules\": [\n    { \"required\": true, \"message\": \"请输入密码\" },\n    { \"min\": 6, \"message\": \"密码长度不能少于6位\" }\n  ]\n}\n```\n\n#### radio — 单选\n```json\n{\n  \"type\": \"radio\",\n  \"field\": \"wizard_install_type\",\n  \"label\": \"安装类型\",\n  \"initValue\": \"standard\",\n  \"options\": [\n    { \"label\": \"标准安装\", \"value\": \"standard\" },\n    { \"label\": \"自定义安装\", \"value\": \"custom\" }\n  ],\n  \"rules\": [{ \"required\": true, \"message\": \"请选择安装类型\" }]\n}\n```\n\n#### checkbox — 多选\n```json\n{\n  \"type\": \"checkbox\",\n  \"field\": \"wizard_modules\",\n  \"label\": \"安装模块\",\n  \"initValue\": [\"web\", \"api\"],\n  \"options\": [\n    { \"label\": \"Web界面\", \"value\": \"web\" },\n    { \"label\": \"API接口\", \"value\": \"api\" },\n    { \"label\": \"数据库\", \"value\": \"database\" }\n  ],\n  \"rules\": [{ \"required\": true, \"message\": \"请至少选择一个模块\" }]\n}\n```\n\n#### select — 下拉选择\n```json\n{\n  \"type\": \"select\",\n  \"field\": \"wizard_database_type\",\n  \"label\": \"数据库类型\",\n  \"initValue\": \"sqlite\",\n  \"options\": [\n    { \"label\": \"SQLite (推荐)\", \"value\": \"sqlite\" },\n    { \"label\": \"MySQL\", \"value\": \"mysql\" },\n    { \"label\": \"PostgreSQL\", \"value\": \"postgresql\" }\n  ],\n  \"rules\": [{ \"required\": true, \"message\": \"请选择数据库类型\" }]\n}\n```\n\n#### switch — 开关\n```json\n{\n  \"type\": \"switch\",\n  \"field\": \"wizard_enable_backup\",\n  \"label\": \"启用自动备份\",\n  \"initValue\": \"true\"\n}\n```\n\n#### tips — 提示文本（不收集输入）\n```json\n{\n  \"type\": \"tips\",\n  \"helpText\": \"请阅读 <a target=\\\"_blank\\\" href=\\\"https://example.com/privacy\\\">隐私政策</a>。\"\n}\n```\n\n### 10.4 验证规则\n\n| 规则 | 示例 |\n|------|------|\n| 必填 | {\\\"required\\\": true, \\\"message\\\": \\\"此字段不能为空\\\"} |\n| 最小长度 | {\\\"min\\\": 3, \\\"message\\\": \\\"长度不能少于3\\\"} |\n| 最大长度 | {\\\"max\\\": 50, \\\"message\\\": \\\"长度不能超过50\\\"} |\n| 精确长度 | {\\\"len\\\": 6, \\\"message\\\": \\\"请输入6位验证码\\\"} |\n| 正则 | {\\\"pattern\\\": \\\"^[a-zA-Z0-9_]+$\\\", \\\"message\\\": \\\"只能含字母数字下划线\\\"} |\n\n### 10.5 安装向导完整示例\n\n```json\n[\n  {\n    \\\"stepTitle\\\": \\\"欢迎安装\\\",\n    \\\"items\\\": [\n      {\n        \\\"type\\\": \\\"tips\\\",\n        \\\"helpText\\\": \\\"欢迎使用我们的应用！在开始使用前，请阅读并同意我们的服务条款。\\\"\n      },\n      {\n        \\\"type\\\": \\\"switch\\\",\n        \\\"field\\\": \\\"wizard_agree_terms\\\",\n        \\\"label\\\": \\\"我已阅读并同意服务条款\\\",\n        \\\"rules\\\": [\n          { \\\"required\\\": true, \\\"message\\\": \\\"请同意服务条款\\\" }\n        ]\n      }\n    ]\n  },\n  {\n    \\\"stepTitle\\\": \\\"创建管理员账号\\\",\n    \\\"items\\\": [\n      {\n        \\\"type\\\": \\\"text\\\",\n        \\\"field\\\": \\\"wizard_admin_username\\\",\n        \\\"label\\\": \\\"管理员用户名\\\",\n        \\\"initValue\\\": \\\"admin\\\",\n        \\\"rules\\\": [\n          { \\\"required\\\": true, \\\"message\\\": \\\"请输入管理员用户名\\\" },\n          { \\\"pattern\\\": \\\"^[a-zA-Z0-9_]+$\\\", \\\"message\\\": \\\"只能包含字母、数字和下划线\\\" }\n        ]\n      },\n      {\n        \\\"type\\\": \\\"password\\\",\n        \\\"field\\\": \\\"wizard_admin_password\\\",\n        \\\"label\\\": \\\"管理员密码\\\",\n        \\\"rules\\\": [\n          { \\\"required\\\": true, \\\"message\\\": \\\"请输入密码\\\" },\n          { \\\"min\\\": 8, \\\"message\\\": \\\"密码长度不能少于8位\\\" }\n        ]\n      },\n      {\n        \\\"type\\\": \\\"password\\\",\n        \\\"field\\\": \\\"wizard_admin_password_confirm\\\",\n        \\\"label\\\": \\\"确认密码\\\",\n        \\\"rules\\\": [\n          { \\\"required\\\": true, \\\"message\\\": \\\"请确认密码\\\" }\n        ]\n      }\n    ]\n  },\n  {\n    \\\"stepTitle\\\": \\\"应用配置\\\",\n    \\\"items\\\": [\n      {\n        \\\"type\\\": \\\"select\\\",\n        \\\"field\\\": \\\"wizard_database_type\\\",\n        \\\"label\\\": \\\"数据库类型\\\",\n        \\\"initValue\\\": \\\"sqlite\\\",\n        \\\"options\\\": [\n          { \\\"label\\\": \\\"SQLite (推荐，无需额外配置)\\\", \\\"value\\\": \\\"sqlite\\\" },\n          { \\\"label\\\": \\\"MySQL\\\", \\\"value\\\": \\\"mysql\\\" }\n        ]\n      },\n      {\n        \\\"type\\\": \\\"text\\\",\n        \\\"field\\\": \\\"wizard_app_port\\\",\n        \\\"label\\\": \\\"应用端口\\\",\n        \\\"initValue\\\": \\\"8080\\\",\n        \\\"rules\\\": [\n          { \\\"required\\\": true, \\\"message\\\": \\\"请输入端口号\\\" },\n          { \\\"pattern\\\": \\\"^[0-9]+$\\\", \\\"message\\\": \\\"端口号必须是数字\\\" }\n        ]\n      }\n    ]\n  }\n]\n```\n\n### 10.6 卸载向导示例\n\n```json\n[\n  {\n    \\\"stepTitle\\\": \\\"确认卸载\\\",\n    \\\"items\\\": [\n      {\n        \\\"type\\\": \\\"tips\\\",\n        \\\"helpText\\\": \\\"您即将卸载此应用。请选择如何处理应用数据：\\\"\n      },\n      {\n        \\\"type\\\": \\\"radio\\\",\n        \\\"field\\\": \\\"wizard_data_action\\\",\n        \\\"label\\\": \\\"数据保留选项\\\",\n        \\\"initValue\\\": \\\"keep\\\",\n        \\\"options\\\": [\n          { \\\"label\\\": \\\"保留数据（推荐）- 将来重新安装时可恢复\\\", \\\"value\\\": \\\"keep\\\" },\n          { \\\"label\\\": \\\"删除所有数据 - 此操作不可恢复！\\\", \\\"value\\\": \\\"delete\\\" }\n        ],\n        \\\"rules\\\": [\n          { \\\"required\\\": true, \\\"message\\\": \\\"请选择数据保留选项\\\" }\n        ]\n      },\n      {\n        \\\"type\\\": \\\"tips\\\",\n        \\\"helpText\\\": \\\"<strong>警告：</strong> 选择删除数据后，所有应用数据将永久丢失，无法恢复。\\\"\n      }\n    ]\n  }\n]\n```\n\n### 10.7 获取用户输入\n\n```bash\n# 用户输入直接作为环境变量使用\nADMIN_USERNAME=\\\"\\$wizard_admin_username\\\"\nADMIN_PASSWORD=\\\"\\$wizard_admin_password\\\"\nDATABASE_TYPE=\\\"\\$wizard_database_type\\\"\nAPP_PORT=\\\"\\$wizard_app_port\\\"\n```\n\n## 11. 图标规范\n\n### 11.1 包文件图标（根目录）\n\n| 文件 | 尺寸 | 格式 | 必填 |\n|------|------|------|------|\n| `ICON.PNG` | 64×64 像素 | PNG，不透明 | ✅ |\n| `ICON_256.PNG` | 256×256 像素 | PNG，不透明 | ✅ |\n\n> 含圆角矩形背景的 PSD 源文件可下载：https://static.fnnas.com/appcenter-marketing/fnpack_ICON_256.zip\n\n### 11.2 UI 入口图标（app/ui/images/）\n\n| 文件 | 尺寸 | 命名规则 |\n|------|------|----------|\n| `icon_64.png` | 64×64 | **小写**，配置中 `{0}` 替换为尺寸 |\n| `icon_256.png` | 256×256 | 如 `images/icon_{0}.png` → 自动选取 |\n\n---\n\n## 12. Docker 应用构建详解\n\n### 12.1 创建项目\n\n```bash\nfnpack create my-app --template docker\n```\n\n### 12.2 目录结构\n\n```\nmy-app/\n├── app/\n│   ├── docker/\n│   │   └── docker-compose.yaml\n│   └── ui/\n│       ├── images/\n│       └── config\n├── manifest\n├── cmd/\n├── config/\n│   ├── privilege\n│   └── resource\n├── wizard/\n├── LICENSE\n├── ICON.PNG\n└── ICON_256.PNG\n```\n\n### 12.3 docker-compose.yaml 示例\n\n```yaml\nversion: '3.8'\n\nservices:\n  web:\n    image: myapp:latest\n    container_name: myapp-web\n    ports:\n      - \"${TRIM_SERVICE_PORT}:80\"\n    volumes:\n      - \"${TRIM_PKGVAR}:/app/data\"\n      - \"${TRIM_DATA_SHARE_PATHS}:/app/shares\"\n    environment:\n      - DB_HOST=db\n      - APP_PORT=${TRIM_SERVICE_PORT}\n\n  db:\n    image: mysql:8.0\n    container_name: myapp-db\n    environment:\n      - MYSQL_ROOT_PASSWORD=${wizard_db_password}\n      - MYSQL_DATABASE=myapp\n    volumes:\n      - db_data:/var/lib/mysql\n\nvolumes:\n  db_data:\n```\n\n> `docker-compose.yaml` 支持使用全部系统环境变量和向导变量。\n> 系统统一管理 compose 的启动/停止，`cmd/main` 只需要实现 status 检查。\n\n### 12.4 资源配置\n\n```json\n{\n  \"docker-project\": {\n    \"projects\": [\n      {\n        \"name\": \"myapp-stack\",\n        \"path\": \"docker\"\n      }\n    ]\n  }\n}\n```\n\n---\n\n## 13. 统一网关（fnOS V1.1.3100+）\n\n统一网关为应用提供稳定的访问入口，无需新增端口监听。HTTP 和 WebSocket 均可接入。\n\n### 13.1 接入方式\n\n在 `app/ui/config` 中声明 `gatewayPrefix` 和 `gatewaySocket`：\n\n```json\n{\n  \".url\": {\n    \"myapp.main\": {\n      \"title\": \"我的应用\",\n      \"icon\": \"images/icon_{0}.png\",\n      \"type\": \"iframe\",\n      \"protocol\": \"\",\n      \"gatewaySocket\": \"app.sock\",\n      \"gatewayPrefix\": \"/app/myapp\",\n      \"url\": \"/app/myapp\",\n      \"allUsers\": true\n    }\n  }\n}\n```\n\n#### 字段说明\n| 字段 | 条件 | 说明 |\n|------|------|------|\n| `gatewayPrefix` | 两者均非空时注册 | 网关访问前缀，格式 `/app/{appname}/{customPath}` 或 `/app/{appname}`，不能包含 `.` |\n| `gatewaySocket` | 两者均非空时注册 | Socket 文件名，如 `app.sock`，放在应用 target 目录 |\n\n### 13.2 WebSocket 支持\n\n```json\n{\n  \"myapp.chat\": {\n    \"title\": \"聊天应用\",\n    \"icon\": \"images/icon_{0}.png\",\n    \"type\": \"iframe\",\n    \"protocol\": \"\",\n    \"gatewaySocket\": \"chat.sock\",\n    \"gatewayPrefix\": \"/app/chat\",\n    \"url\": \"/app/chat\",\n    \"allUsers\": true\n  }\n}\n```\n\n前端连接示例：\n```javascript\nconst wsProtocol = window.location.protocol === \"https:\" ? \"wss:\" : \"ws:\";\nconst wsUrl = `${wsProtocol}//${window.location.host}/app/chat/ws`;\nconst socket = new WebSocket(wsUrl);\n```\n\n> WebSocket 路由建议固定为网关前缀下的子路径（如 `/ws`），不要信任客户端主动上报的用户 ID。\n\n### 13.3 登录认证\n\n统一网关转发请求前完成登录态校验，认证通过后增加以下 Header：\n\n| Header | 说明 | 示例 |\n|--------|------|------|\n| `X-Trim-Userid` | 当前登录用户 UID | `1000` |\n| `X-Trim-Isadmin` | 是否管理员 | `true` / `false` |\n| `X-Trim-Username` | 当前登录用户名 | `admin` |\n\nNode.js 获取用户信息：\n```javascript\nfunction getGatewayUser(req) {\n  return {\n    uid: req.headers[\"x-trim-userid\"],\n    isAdmin: req.headers[\"x-trim-isadmin\"] === \"true\",\n    username: req.headers[\"x-trim-username\"]\n  };\n}\n```\n\n> 应用仍需要自己的权限判断逻辑（数据隔离、管理员接口、高风险操作校验）。\n\n### 13.4 不鉴权接口\n\n公开资源、OAuth 回调等无需登录态的特殊接口：\n- 只开放必要路径\n- 只允许必要 HTTP 方法\n- 不返回用户敏感信息\n- 不提供写入/删除等高危能力\n\n### 13.5 静态文件安全\n- 路径标准化处理\n- 禁止 `..` 访问上级目录\n- 限制可访问目录范围\n- 不暴露配置文件、密钥、数据库\n- 可下载文件类型做白名单控制\n\n---\n\n## 14. 运行时环境与中间件\n\n### 14.1 运行时环境\n\n通过 `manifest` 的 `install_dep_apps` 声明依赖，系统确保安装和启动时目标环境已就绪。\n\n| 运行时 | manifest 声明 | PATH 配置 |\n|--------|---------------|-----------|\n| Node.js v22 | `install_dep_apps=nodejs_v22` | `export PATH=/var/apps/nodejs_v22/target/bin:$PATH` |\n| Node.js v20 | `install_dep_apps=nodejs_v20` | `export PATH=/var/apps/nodejs_v20/target/bin:$PATH` |\n| Node.js v18 | `install_dep_apps=nodejs_v18` | `export PATH=/var/apps/nodejs_v18/target/bin:$PATH` |\n| Python 3.12 | `install_dep_apps=python312` | `export PATH=/var/apps/python312/target/bin:$PATH` |\n| Python 3.11 | `install_dep_apps=python311` | `export PATH=/var/apps/python311/target/bin:$PATH` |\n| Python 3.10 | `install_dep_apps=python310` | `export PATH=/var/apps/python310/target/bin:$PATH` |\n| Java 21 | `install_dep_apps=java-21-openjdk` | `export PATH=/var/apps/java-21-openjdk/target/bin:$PATH` |\n| Java 17 | `install_dep_apps=java-17-openjdk` | `export PATH=/var/apps/java-17-openjdk/target/bin:$PATH` |\n\n**Python 虚拟环境最佳实践：**\n```bash\nexport PATH=/var/apps/python312/target/bin:$PATH\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\n```\n\n### 14.2 中间件服务\n\n| 中间件 | manifest 声明 | 连接信息 |\n|--------|---------------|----------|\n| Redis | `install_dep_apps=redis` | `127.0.0.1:6379` |\n| MinIO | `install_dep_apps=minio` | `127.0.0.1:9000` |\n| RabbitMQ | `install_dep_apps=rabbitmq` | `127.0.0.1:5672`，默认 `guest/guest` |\n| MariaDB | `install_dep_apps=mariaDB` | 即将上线 |\n\n#### Redis 使用示例（Python）\n```python\nimport redis\n\npool = redis.ConnectionPool(\n    host='127.0.0.1', port=6379, db=1,\n    decode_responses=True, max_connections=10\n)\nclient = redis.Redis(connection_pool=pool)\nclient.lpush('my_list', 'item1', 'item2')\nitems = client.lrange('my_list', 0, -1)\n```\n\n#### MinIO 使用示例（Python）\n```python\nfrom minio import Minio\n\nclient = Minio(\n    endpoint=\"127.0.0.1:9000\",\n    access_key=\"your_access_key\",\n    secret_key=\"your_secret_key\",\n    secure=False\n)\nif not client.bucket_exists(\"my-bucket\"):\n    client.make_bucket(\"my-bucket\")\n```\n\n#### RabbitMQ 使用示例（Python）\n```python\nimport pika\n\nconnection = pika.BlockingConnection(pika.ConnectionParameters(\n    host=\"127.0.0.1\", port=5672,\n    virtual_host=\"/\",\n    credentials=pika.PlainCredentials(\"guest\", \"guest\")\n))\nchannel = connection.channel()\nchannel.queue_declare(queue=\"my_queue\")\nchannel.basic_publish(exchange=\"\", routing_key=\"my_queue\", body=\"Hello\")\n```\n\n---\n\n## 15. 应用依赖关系\n\n### 15.1 声明依赖\n\n在 `manifest` 中使用 `install_dep_apps` 字段：\n\n```ini\n# 格式：app1>2.2.2:app2:app3\n# > 表示最低版本要求\n# : 分隔多个依赖\ninstall_dep_apps = dep2:dep1\n```\n\n### 15.2 依赖检查逻辑\n\n| 操作 | 行为 |\n|------|------|\n| 安装/启用 | 检查依赖是否已安装启用，未安装则自动安装，未启用则自动启用 |\n| 停用/卸载 | 检查是否有其他应用依赖本应用，有则提示自动停用 |\n| 更新 | 检查是否有其他应用依赖，有则在更新期间自动停用 |\n\n### 15.3 依赖顺序\n\n自动安装和启用的顺序**从后往前**：\n```ini\n# 先安装 dep1，后安装 dep2\ninstall_dep_apps = dep2:dep1\n```\n\n### 15.4 嵌套依赖\n\n> 应用中心仅对一层依赖进行检查，不做递归检查。\n\n如果 A 依赖 B，B 依赖 C，则 A 需要同时声明 B 和 C：\n```ini\ninstall_dep_apps = depB:depC\n```\n\n---\n\n## 16. Native 应用完整构建流程\n\n### 16.1 开发 → 打包流程\n\n1. 开发应用代码（如 Node.js + Express）\n2. 编译打包到 `dist/` 目录\n3. `fnpack create <appname>` 创建打包目录\n4. 将编译产物复制到 `app/server/`\n5. 编辑 `manifest`、`config/privilege`、`config/resource`\n6. 编写 `cmd/main` 生命周期脚本\n7. 配置 `app/ui/config` 入口\n8. 添加 `app/ui/images/icon_64.png` 和 `icon_256.png`\n9. 更新根目录 `ICON.PNG` 和 `ICON_256.PNG`\n10. `fnpack build` 打包\n\n### 16.2 集成到编译脚本\n\n```javascript\n// scripts/build-combined.js（Node.js 项目）\nconst packDir = path.join(root, 'fnnas.notepad')\nconst packServerDir = path.join(packDir, 'app', 'server');\nrun(`rm -rf ${packServerDir}`);\nrun(`mkdir ${packServerDir}`);\nrun(`cp -r ${outDir}/* ${packServerDir}/`);\nrun(`fnpack build -d ${packDir}`);\n```\n\n### 16.3 纯静态页面 Native 应用\n\n对于无后台服务的静态页面应用，`cmd/main` 只需：\n```bash\n#!/bin/bash\n\ncase $1 in\nstart)   exit 0 ;;  # 无进程需要启动\nstop)    exit 0 ;;  # 无进程需要停止\nstatus)  exit 0 ;;  # 静态页面始终\"运行中\"\n*)       exit 1 ;;\nesac\n```\n\n> 此时的 CGI 脚本负责处理 HTTP 请求，从 `app/www/` 中读取并返回静态文件。\n\n---\n\n## 17. 打包与校验\n\n### 17.1 基本打包\n\n```bash\ncd myapp\nfnpack build                    # 当前目录打包\nfnpack build --directory <path> # 指定目录打包\n```\n\n### 17.2 打包校验规则\n\n| 路径 | 校验要求 |\n|------|----------|\n| `manifest` | 必须存在，必选字段存在 |\n| `config/privilege` | 必须存在，符合 JSON 格式 |\n| `config/resource` | 必须存在，符合 JSON 格式 |\n| `ICON.PNG` | 必须存在 |\n| `ICON_256.PNG` | 必须存在 |\n| `app/` | 目录必须存在 |\n| `cmd/` | 目录必须存在 |\n| `wizard/` | 目录必须存在 |\n| `app/{desktop_uidir}/` | 若 manifest 定义，目录必须存在 |\n\n### 17.3 输出\n\n打包后在当前目录生成 `{appname}.fpk` 文件。\n\n---\n\n## 18. 测试安装\n\n### 方式一：install-fpk（上传后安装）\n\n```bash\n# 将 fpk 文件上传到 fnOS 设备\nappcenter-cli install-fpk myapp.fpk\n\n# 静默安装（跳过向导）\nappcenter-cli install-fpk myapp.fpk --env config.env\n```\n\n环境变量文件 `config.env` 格式：\n```ini\n# 应用配置\nwizard_admin_username=admin\nwizard_admin_password=mypassword123\nwizard_database_type=sqlite\nwizard_app_port=8080\nwizard_agree_terms=true\n```\n\n### 方式二：install-local（开发测试）\n\n```bash\n# 在应用目录中直接安装，无需打包\ncd /path/to/myapp\nappcenter-cli install-local\n```\n\n### 检查日志\n\n日志位置：`/var/apps/{appname}/var/info.log`\n\n---\n\n## 19. 上架发布\n\n开发者后台即将上线。当前可通过加入**应用中心开发者先锋交流群**，联系专员办理应用内测和上架。\n\n---\n\n## 20. 文档更新历史\n\n| 版本 | 日期 | 主要内容 |\n|------|------|----------|\n| 20251216 | 2025-12-16 | manifest 新增 changelog；fnpack 更新至 1.0.4；新增搜索；优化创建应用教学案例（HelloFnosAppCenter） |\n| 20251231 | 2025-12-31 | 新增 New!/Update! 徽标；arch 废弃，platform 字段替代；入口配置支持环境变量；protocol 支持空字符串（自适应）；fnpack 1.2.0（新增 Linux ARM，补全校验错误处理）；新增错误异常展示处理（$TRIM_TEMP_LOGFILE）；框架文档结构调整 |\n| 20260509 | 2026-05-09 | 新增统一网关注册文档；新增登录认证文档（fnOS V1.1.3100+） |\n| 20260604 | 2026-06-04 | 逐页复审官方完整文档，补充环境变量完整使用示例、manifest 等号对齐格式示例、control.accessPerm 字段说明、Docker main 脚本完善示例、CGI 集合仓库链接；修正 Redis 端口文档笔误说明 |\n\n---\n\n## 附录：命令行速查\n\n```bash\n# === fnpack 打包 ===\nfnpack create <appname>                                    # 创建 Native 项目\nfnpack create <appname> --template docker                  # 创建 Docker 项目\nfnpack create <appname> --without-ui true                  # 纯服务项目\nfnpack build                                               # 打包\nfnpack build --directory <path>                            # 指定目录打包\n\n# === appcenter-cli 管理 ===\nappcenter-cli install-fpk <file.fpk>                       # 安装 fpk\nappcenter-cli install-fpk <file.fpk> --env config.env      # 静默安装\nappcenter-cli install-local                                # 本地目录安装\nappcenter-cli list                                         # 已安装列表\nappcenter-cli start <appname>                              # 启动\nappcenter-cli stop <appname>                               # 停止\nappcenter-cli manual-install [enable|disable]              # 手动安装开关\nappcenter-cli default-volume [number]                      # 默认存储空间\n```\n\nFile v1.2.2:_meta.json\n\n{\n  \"ownerId\": \"kn77pvxj7zf4hrd6q5ydgn2js5826rny\",\n  \"slug\": \"fn-fpk\",\n  \"version\": \"1.2.2\",\n  \"publishedAt\": 1780587031586\n}\n\nFile v1.2.2:skill-card.md\n\n## Description: <br>\nHelps developers build and package Feiniu NAS fnOS third-party FPK applications, covering native and Docker app setup, manifests, permissions, lifecycle scripts, gateways, packaging, testing, and release preparation. <br>\n\nThis skill is ready for commercial/non-commercial use. <br>\n\n## Publisher: <br>\n[dalingo81](https://clawhub.ai/user/dalingo81) <br>\n\n### License/Terms of Use: <br>\nMIT-0 <br>\n\n\n## Use Case: <br>\nDevelopers building Feiniu NAS fnOS applications use this skill to prepare FPK app projects, configure manifests and permissions, write lifecycle scripts, package apps, and test local installation workflows. <br>\n\n### Deployment Geography for Use: <br>\nGlobal <br>\n\n## Known Risks and Mitigations: <br>\nRisk: The skill includes shell commands and NAS administration workflows that can change installed applications or system settings. <br>\nMitigation: Review commands before running them on a NAS and use them only when intentionally developing or testing fnOS FPK packages. <br>\nRisk: Root-mode packaging and wizard credentials can expose broad system access or sensitive secrets if used carelessly. <br>\nMitigation: Avoid root-mode app packaging unless required, and treat wizard passwords and service credentials as secrets. <br>\n\n\n## Reference(s): <br>\n- [Feiniu NAS Developer Documentation](https://developer.fnnas.com) <br>\n- [fnpack CLI Downloads](https://static2.fnnas.com/fnpack/fnpack-1.2.1-{os}-{arch}) <br>\n- [fnOS App Center CGI Collection](https://github.com/FNOSP/fnosAppCenterCgiCollection) <br>\n\n\n## Skill Output: <br>\n**Output Type(s):** [guidance, markdown, code, shell commands, configuration] <br>\n**Output Format:** [Markdown with inline code blocks and configuration examples] <br>\n**Output Parameters:** [1D] <br>\n**Other Properties Related to Output:** [Documentation-only guidance for fnOS FPK development] <br>\n\n## Skill Version(s): <br>\n1.2.2 (source: ClawHub release evidence) <br>\n\n## Ethical Considerations: <br>\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment. <br>\n\nArchive v1.3.0: 3 files, 20423 bytes\n\nFiles: skill-card.md (2521b), SKILL.md (52790b), _meta.json (125b)\n\nFile v1.3.0:SKILL.md\n\n---\nname: fn-fpk\ndescription: 飞牛NAS (fnOS) FPK 应用打包开发技能。使用此技能开发和打包飞牛NAS第三方应用（.fpk），包括：Native 应用（Node.js/Python/Java/Go/Shell 等）和 Docker 应用。涵盖整个开发周期：开发环境准备、fnpack 创建项目、manifest 配置、权限/资源配置、用户入口配置（应用入口 app/ui/config + 桌面图标 + 文件右键菜单）、生命周期脚本编写（cmd/main）、向导配置（wizard）、图标规范、CGI 反向代理、统一网关注册/认证、运行时环境（Python/Node.js/Java）、中间件服务（Redis/MinIO/RabbitMQ/MariaDB）、依赖管理、fnpack CLI 打包、appcenter-cli 测试安装、到上架发布。用户提到\"飞牛\"、\"fnOS\"、\"FPK\"、\"飞牛应用\"等关键词时触发。\n---\n\n# fn-fpk — 飞牛 NAS fnOS FPK 应用开发\n\n> 基于官方文档 https://developer.fnnas.com 于 2026-06-02 全面更新。\n> 系统架构: x86_64, Linux 内核 6.12.18+, Debian 发行版。\n> fnpack 版本: 1.2.1, appcenter-cli 预装在 fnOS 中。\n\n---\n\n## 1. 开发环境准备\n\n### 1.1 系统要求\n- **fnOS 版本**: ≥ 0.9.27\n- **架构**: 仅支持 x86_64 (AMD64) — 应用的编译选项也需选择 x86_64\n- **存储**: 至少创建一个存储空间\n- **权限**: 管理员权限（安装/卸载/系统设置）\n\n### 1.2 开发方式\n- **本地开发**: 在本地用 fnpack CLI 打包，再传送到 fnOS 设备安装\n- **远程开发**: SSH 到 fnOS 设备直接开发，结合 `appcenter-cli install-local` 快速测试\n\n### 1.3 技术栈\n| 语言/框架 | 支持情况 |\n|-----------|---------|\n| Node.js | ✅ (v14/v16/v18/v20/v22) |\n| Python | ✅ (3.8/3.9/3.10/3.11/3.12) |\n| Java | ✅ (11/17/21 OpenJDK) |\n| Go | ✅ Linux 运行时支持 |\n| Shell | ✅ (Bash) |\n| HTML/JS/CSS | ✅ 前端任意框架 |\n\n### 1.4 CLI 工具\n\n#### fnpack 打包工具\n下载地址：https://static2.fnnas.com/fnpack/fnpack-1.2.1-{os}-{arch}\n\n| 平台 | 二进制 |\n|------|--------|\n| Windows x86 | `fnpack-1.2.1-windows-amd64` |\n| Linux x86 | `fnpack-1.2.1-linux-amd64` |\n| Linux ARM | `fnpack-1.2.1-linux-arm64` |\n| macOS Intel | `fnpack-1.2.1-darwin-amd64` |\n| macOS M 系列 | `fnpack-1.2.1-darwin-arm64` |\n\n安装：\n```bash\nchmod +x fnpack-1.2.1-linux-amd64\nsudo mv fnpack-1.2.1-linux-amd64 /usr/local/bin/fnpack\nfnpack --help\n```\n\n#### appcenter-cli （预装在 fnOS 中）\n```bash\n# 安装 fpk 文件\nappcenter-cli install-fpk myapp.fpk\n\n# 从本地目录安装（开发模式，无需打包）\ncd /path/to/myapp\nappcenter-cli install-local\n\n# 管理应用\nappcenter-cli list              # 已安装列表\nappcenter-cli start myapp       # 启动\nappcenter-cli stop myapp        # 停止\n\n# 手动安装功能（仅测试用途）\nappcenter-cli manual-install             # 查看状态\nappcenter-cli manual-install enable      # 开启\nappcenter-cli manual-install disable     # 关闭\n\n# 设置默认存储空间\nappcenter-cli default-volume             # 查看当前\nappcenter-cli default-volume 1           # 设置存储空间1\n\n# 静默安装（跳过向导）\nappcenter-cli install-fpk myapp.fpk --env config.env\n```\n\n---\n\n## 2. 应用类型\n\n| 类型 | 描述 | 创建命令 |\n|------|------|----------|\n| **Native 应用** | 直接运行在 fnOS 上的应用 | `fnpack create <appname>` |\n| **Docker 应用** | 基于 Docker Compose 容器编排 | `fnpack create <appname> --template docker` |\n| **纯服务应用**（无 UI） | 无 Web 访问入口 | 加 `--without-ui true` |\n\n---\n\n## 3. 项目结构与核心文件\n\n### 3.1 通用结构（fnpack create 生成）\n\n```\nmyapp/\n├── app/                      # 应用可执行文件/资源目录\n│   ├── server/               # 后台服务程序（Native 应用）\n│   ├── ui/                   # Web UI 入口配置\n│   │   ├── images/           # 入口图标（icon_64.png, icon_256.png）\n│   │   └── config            # 入口配置文件（JSON）\n│   ├── www/                  # Web 静态资源（HTML/CSS/JS）\n│   └── docker/               # Docker Compose 文件（Docker 应用）\n│       └── docker-compose.yaml\n├── manifest                  # 应用基本信息（必需）\n├── cmd/                      # 生命周期管理脚本（全部必需）\n│   ├── main                  # 启动/停止/状态检查\n│   ├── install_init          # 安装前初始化\n│   ├── install_callback      # 安装后回调\n│   ├── uninstall_init        # 卸载前\n│   ├── uninstall_callback    # 卸载后\n│   ├── upgrade_init          # 升级前\n│   ├── upgrade_callback      # 升级后\n│   ├── config_init           # 配置变更前\n│   └── config_callback       # 配置变更后\n├── config/\n│   ├── privilege             # 权限配置（JSON，必需）\n│   └── resource              # 资源配置（JSON，必需）\n├── wizard/                   # 向导配置（可选）\n│   ├── install               # 安装向导\n│   ├── uninstall             # 卸载向导\n│   ├── upgrade               # 更新向导\n│   └── config                # 配置向导\n├── ICON.PNG                  # 64×64 应用图标（必需）\n├── ICON_256.PNG              # 256×256 应用图标（必需）\n└── LICENSE                   # 许可证（可选）\n```\n\n### 3.2 安装后的目录结构\n\n当应用安装到 fnOS 后，系统创建如下目录：\n\n```\n/var/apps/[appname]/\n├── cmd/                      # 生命周期脚本（来自包）\n├── config/\n│   ├── privilege             # 权限配置\n│   └── resource              # 资源配置\n├── ICON_256.PNG\n├── ICON.PNG\n├── LICENSE\n├── manifest\n├── etc -> /vol{volume}/@appconf/[appname]     # 静态配置文件\n├── home -> /vol{volume}/@apphome/[appname]    # 用户数据\n├── target -> /vol{volume}/@appcenter/[appname] # 可执行文件\n├── tmp -> /vol{volume}/@apptemp/[appname]     # 临时文件\n├── var -> /vol{volume}/@appdata/[appname]     # 运行时数据\n├── shares/                   # 共享数据目录（按 resource 配置）\n│   ├── datashare1 -> /vol{volume}/@appshare/datashare1\n│   └── datashare2 -> /vol{volume}/@appshare/datashare2\n└── wizard/                   # 向导配置（安装/卸载/升级/配置）\n    ├── install\n    ├── uninstall\n    ├── upgrade\n    └── config\n```\n\n---\n\n## 4. manifest — 应用基本信息\n\n`manifest` 文件是应用的\"身份证\"，放在项目根目录，**无扩展名**。\n\n### 4.1 完整字段参考\n\n```\n# ═══════════════ 基本信息 ═══════════════\nappname        = myapp                        # ① 应用唯一标识，系统全局唯一\nversion        = 1.0.0                        # ② 版本号：x[.y[.z]][-build]\ndisplay_name   = 我的应用                      # ③ 用户可见的名称\ndesc           = 这是一个示例应用              # ④ 详细介绍，支持 HTML 格式\nsource         = thirdparty                   # ⑤ 固定值：thirdparty\n\n# ═══════════════ 系统要求 ═══════════════\nplatform       = x86                          # ⑥ 架构：x86 | arm | all (V1.1.8+)\narch           = x86_64                       # ⑦ 已废弃，请用 platform\nos_min_version = 0.9.0                        # ⑧ 最低系统版本\nos_max_version = 0.9.100                      # ⑨ 最高系统版本\n\n# ═══════════════ 开发者信息 ═══════════════\nmaintainer     = 张三                          # 开发者/团队名称\nmaintainer_url = https://example.com           # 开发者网站\ndistributor    = 示例公司                      # 发布者\ndistributor_url = https://company.com          # 发布者网站\n\n# ═══════════════ 安装与运行控制 ═══════════════\ninstall_type   =                              # 安装位置：空=用户可选存储空间，root=系统分区\nctl_stop       = true                         # 是否显示启动/停止按钮，默认 true\ncheckport      = true                         # 是否启用端口检查，默认 true\nservice_port   = 8080                         # 应用监听端口（单个端口）\ndisable_authorization_path = false            # 是否禁用授权目录功能\n\n# ═══════════════ 用户界面 ═══════════════\ndesktop_uidir          = ui                   # UI 组件目录（相对应用根目录）\ndesktop_applaunchname  = myapp.Application    # 默认启动入口 ID\n\n# ═══════════════ 依赖管理 ═══════════════\ninstall_dep_apps = mariaDB:redis              # 依赖应用列表，格式：app1>2.2.2:app2:app3\n\n# ═══════════════ 应用更新 ═══════════════\nchangelog = 新增了XX功能                       # 更新日志（升级时展示）\n```\n\n### 4.2 字段详解\n\n#### 应用标识\n| 字段 | 必填 | 说明 |\n|------|------|------|\n| `appname` | ✅ | 全局唯一标识符，用于系统识别 |\n| `version` | ✅ | 格式：`x[.y[.z]][-build]`，例如 `1.0.0`、`2.1.3-beta` |\n| `display_name` | ✅ | 应用中心显示的名称 |\n| `desc` | ✅ | 详细介绍，支持 HTML 格式 |\n| `source` | ✅ | 固定为 `thirdparty` |\n\n#### 系统要求（V1.1.8+ 新增 platform 字段）\n| 字段 | 说明 |\n|------|------|\n| `platform = x86` | 仅支持 x86 架构 |\n| `platform = arm` | 仅支持 arm 架构 |\n| `platform = all` | 所有架构，Docker 应用常用 |\n| `arch` | 【已废弃】用 platform 替代 |\n\n#### 安装控制\n| 字段 | 默认值 | 说明 |\n|------|--------|------|\n| `install_type` | 空 | `root` = 安装到系统分区 `/usr/local/apps/@appcenter/`；空 = 用户选择存储位置 |\n| `ctl_stop` | `true` | `false` 时隐藏启动/停止按钮和运行状态（无进程应用） |\n| `checkport` | `true` | `false` 时系统不检查端口占用 |\n| `service_port` | — | 应用监听端口（仅支持单个端口） |\n| `disable_authorization_path` | `false` | `true` 时应用设置页不显示授权目录操作 |\n\n#### 依赖管理\n```ini\n# 格式：app1>2.2.2:app2:app3\n# > 表示最低版本要求\n# : 分隔多个依赖\n# 系统按列表顺序自动安装依赖\ninstall_dep_apps = mariaDB:redis\n```\n\n---\n\n## 5. 权限配置 (config/privilege)\n\n`config/privilege` 文件定义应用运行时的权限级别和用户身份，JSON 格式，**必需**。\n\n### 5.1 默认权限模式（推荐）\n\n```json\n{\n  \"defaults\": {\n    \"run-as\": \"package\"\n  },\n  \"username\": \"myapp_user\",\n  \"groupname\": \"myapp_group\"\n}\n```\n\n| 字段 | 默认值 | 说明 |\n|------|--------|------|\n| `run-as` | `package` | `package` = 应用用户模式，`root` = root 模式 |\n| `username` | appname | 应用专用用户名 |\n| `groupname` | appname | 应用专用用户组名 |\n\n#### 默认模式行为\n- 系统为应用创建专用用户和用户组\n- 所有应用进程以专用用户身份运行\n- 应用文件所有者是该专用用户\n- 应用只能访问自己的目录和系统允许的公共资源\n\n### 5.2 Root 权限模式\n\n> ⚠️ 仅飞牛官方合作的企业开发者可使用，第三方应用默认无法在应用中心发布 root 权限应用。\n\n```json\n{\n  \"defaults\": {\n    \"run-as\": \"root\"\n  },\n  \"username\": \"myapp_user\",\n  \"groupname\": \"myapp_group\"\n}\n```\n\n#### Root 模式行为\n- 应用脚本以 root 身份执行\n- 应用进程可以 root 身份或指定应用用户身份运行\n- 应用文件所有者变为 root\n- 系统仍会创建应用专用用户和用户组\n\n### 5.3 外部文件访问权限\n\n应用默认无法访问用户个人文件。用户需要在应用设置中授权：\n- **读写权限**：读取和修改文件\n- **只读权限**：只能读取，不能修改\n- **禁止访问**：无法访问该路径\n\n也可通过 `config/resource` 的 `data-share` 设置默认共享目录。\n\n### 5.4 当前用户检查\n\n```bash\necho \"当前运行用户: $TRIM_RUN_USERNAME\"\necho \"应用专用用户: $TRIM_USERNAME\"\n```\n\n---\n\n## 6. 资源配置 (config/resource)\n\n`config/resource` 文件声明应用的扩展能力，JSON 格式，**必需**。\n\n### 6.1 数据共享 (data-share)\n\n创建共享目录，用户通过 **文件管理 → 应用文件** 可访问，应用也可实时访问。\n\n```json\n{\n  \"data-share\": {\n    \"shares\": [\n      {\n        \"name\": \"documents\",\n        \"permission\": {\n          \"rw\": [\"myapp_user\"]\n        }\n      },\n      {\n        \"name\": \"documents/backups\",\n        \"permission\": {\n          \"ro\": [\"myapp_user\"]\n        }\n      }\n    ]\n  }\n}\n```\n\n| 字段 | 说明 |\n|------|------|\n| `name` | 共享目录名，支持多级（如 `documents/backups`） |\n| `permission.rw` | 读写权限用户列表 |\n| `permission.ro` | 只读权限用户列表 |\n\n### 6.2 系统集成 (usr-local-linker)\n\n应用启动时自动创建软链接到系统目录，停止时自动移除。\n\n```json\n{\n  \"usr-local-linker\": {\n    \"bin\": [\n      \"bin/myapp-cli\",\n      \"bin/myapp-server\"\n    ],\n    \"lib\": [\n      \"lib/mylib.so\",\n      \"lib/mylib.a\"\n    ],\n    \"etc\": [\n      \"etc/myapp.conf\",\n      \"etc/myapp.d/default.conf\"\n    ]\n  }\n}\n```\n\n| 链接 | 目标目录 |\n|------|----------|\n| `bin` | `/usr/local/bin/` |\n| `lib` | `/usr/local/lib/` |\n| `etc` | `/usr/local/etc/` |\n\n### 6.3 Docker 项目支持 (docker-project)\n\nDocker 应用需要在 `config/resource` 中声明：\n\n```json\n{\n  \"docker-project\": {\n    \"projects\": [\n      {\n        \"name\": \"myapp-stack\",\n        \"path\": \"docker\"\n      }\n    ]\n  }\n}\n```\n\n| 字段 | 说明 |\n|------|------|\n| `name` | Docker Compose 项目名称 |\n| `path` | 相对于 app 目录的路径，指向含 `docker-compose.yaml` 的文件夹 |\n\n---\n\n## 7. 入口配置 (app/ui/config)\n\n应用入口是用户访问应用的\"大门\"。配置文件位于 `app/ui/config`（JSON 格式），入口键名必须以 `appname` 为前缀。\n\n### 7.1 桌面图标入口\n\n```json\n{\n  \".url\": {\n    \"myapp.main\": {\n      \"title\": \"我的应用\",\n      \"icon\": \"images/icon_{0}.png\",\n      \"type\": \"url\",\n      \"protocol\": \"http\",\n      \"port\": \"8080\",\n      \"url\": \"/\",\n      \"allUsers\": true\n    },\n    \"myapp.admin\": {\n      \"title\": \"管理后台\",\n      \"icon\": \"images/admin_icon_{0}.png\",\n      \"type\": \"url\",\n      \"protocol\": \"http\",\n      \"port\": \"8080\",\n      \"url\": \"/admin\",\n      \"allUsers\": false\n    }\n  }\n}\n```\n\n### 7.2 文件右键入口\n\n```json\n{\n  \".url\": {\n    \"myapp.editor\": {\n      \"title\": \"文本编辑器\",\n      \"icon\": \"images/editor-{0}.png\",\n      \"type\": \"url\",\n      \"protocol\": \"http\",\n      \"port\": \"8080\",\n      \"url\": \"/edit\",\n      \"allUsers\": true,\n      \"fileTypes\": [\"txt\", \"md\", \"json\", \"xml\"],\n      \"noDisplay\": true\n    }\n  }\n}\n```\n\n`fileTypes` + `noDisplay: true` 实现\"只在文件右键菜单中显示，不在桌面显示\"。\n\n### 7.3 CGI 入口（推荐用于静态页面/Native 应用）\n\n```json\n{\n  \".url\": {\n    \"myapp.Application\": {\n      \"title\": \"我的应用\",\n      \"icon\": \"images/icon_{0}.png\",\n      \"type\": \"iframe\",\n      \"protocol\": \"http\",\n      \"url\": \"/cgi/ThirdParty/myapp/index.cgi/\",\n      \"allUsers\": true\n    }\n  }\n}\n```\n\n> CGI 方案不需要声明 `port` 字段。\n\n### 7.4 入口字段参考\n\n| 字段 | 必填 | 说明 |\n|------|------|------|\n| `title` | ✅ | 入口显示标题（桌面图标名称/右键菜单名称） |\n| `icon` | ✅ | 图标路径（相对 UI 目录），`{0}` 会被替换为 `64` 或 `256` |\n| `type` | ✅ | `url` = 新标签页打开，`iframe` = 桌面窗口内嵌 |\n| `protocol` | ✅ | `http` / `https` / `\"\"`（空字符串=自适应协议；不声明则默认 `http`） |\n| `port` | ✅ | 端口号；CGI 方案无需声明；可使用 `${wizard_port}` 动态配置 (V1.1.8+) |\n| `url` | ✅ | 访问路径（相对路径）；可使用 `${wizard_url}` 动态配置 (V1.1.8+) |\n| `allUsers` | ✅ | `true` = 所有用户可见，`false` = 仅管理员可见 |\n| `fileTypes` | ❌ | 文件右键关联的文件扩展名数组，如 `[\"txt\",\"md\"]` |\n| `noDisplay` | ❌ | `true` = 不在桌面显示，仅右键菜单显示 |\n| `accessPerm` | ❌ | 桌面访问设置权限：`editable` / `readonly` / `hidden` |\n| `gatewaySocket` | ❌ | 统一网关 Socket 文件名 |\n| `gatewayPrefix` | ❌ | 统一网关访问前缀，如 `/app/myapp` |\n\n#### 入口配置文件支持环境变量 (V1.1.8+)\n```json\n{\n  \"myapp.configurable\": {\n    \"title\": \"可配置应用\",\n    \"port\": \"${wizard_port}\",\n    \"url\": \"${wizard_path}\"\n  }\n}\n```\n\n#### 控制字段\n```json\n{\n  \"myapp.advanced\": {\n    \"title\": \"高级功能\",\n    \"control\": {\n      \"accessPerm\": \"readonly\"\n    }\n  }\n}\n```\n\n### 7.5 文件路径参数\n\n通过右键菜单打开文件时，系统自动在 URL 后拼接 `path` 参数：\n```\nhttp://localhost:8080/edit?path=/vol1/Users/admin/Documents/example.txt\n```\n\n### 7.6 CGI 脚本示例\n\n`app/ui/index.cgi` — 通过 Shell 脚本实现静态资源转发：\n\n```bash\n#!/bin/bash\n\n# 【注意】修改为你的静态文件根目录\nBASE_PATH=\"/var/apps/App.Native.HelloFnosAppCenter/target/www\"\n\n# 从 REQUEST_URI 里拿到 index.cgi 后面的路径\nURI_NO_QUERY=\"${REQUEST_URI%%\\?*}\"\nREL_PATH=\"/\"\n\ncase \"$URI_NO_QUERY\" in\n  *index.cgi*)\n    REL_PATH=\"${URI_NO_QUERY#*index.cgi}\"\n    ;;\nesac\n\nif [ -z \"$REL_PATH\" ] || [ \"$REL_PATH\" = \"/\" ]; then\n  REL_PATH=\"/index.html\"\nfi\n\nTARGET_FILE=\"${BASE_PATH}${REL_PATH}\"\n\n# 防御 .. 越级访问\nif echo \"$TARGET_FILE\" | grep -q '\\.\\.'; then\n  echo \"Status: 400 Bad Request\"\n  echo \"Content-Type: text/plain; charset=utf-8\"\n  echo \"\"\n  echo \"Bad Request\"\n  exit 0\nfi\n\nif [ ! -f \"$TARGET_FILE\" ]; then\n  echo \"Status: 404 Not Found\"\n  echo \"Content-Type: text/plain; charset=utf-8\"\n  echo \"\"\n  echo \"404 Not Found: ${REL_PATH}\"\n  exit 0\nfi\n\n# 根据扩展名判断 Content-Type（精简版，完整版见社区参考）\next=\"${TARGET_FILE##*.}\"\ncase \"$ext\" in\n  html|htm)  mime=\"text/html; charset=utf-8\" ;;\n  css)       mime=\"text/css; charset=utf-8\" ;;\n  js)        mime=\"application/javascript; charset=utf-8\" ;;\n  png)       mime=\"image/png\" ;;\n  jpg|jpeg)  mime=\"image/jpeg\" ;;\n  *)         mime=\"application/octet-stream\" ;;\nesac\n\necho \"Content-Type: $mime\"\necho \"\"\ncat \"$TARGET_FILE\"\n```\n\n> 更多 CGI 实现参考：https://github.com/FNOSP/fnosAppCenterCgiCollection\n\n---\n\n## 8. 生命周期脚本 (cmd/)\n\n系统通过调用 `cmd/` 目录下的脚本来管理应用的全生命周期。所有脚本均为 Bash 脚本，全部**必需**（即使只写 `exit 0`）。\n\n> 注意：不要用 `echo` 直接输出错误信息，而是写入 `$TRIM_TEMP_LOGFILE`；不要直接 `exit`，而是返回错误码 `1`。\n\n### 8.1 cmd/main — 启动/停止/状态检查\n\n```bash\n#!/bin/bash\n\ncase $1 in\nstart)\n  # 启动应用的命令，成功返回 0，失败返回 1\n  exit 0\n  ;;\nstop)\n  # 停止应用的命令，成功返回 0，失败返回 1\n  exit 0\n  ;;\nstatus)\n  # 检查应用运行状态，运行中返回 0，未运行返回 3\n  exit 0\n  ;;\n*)\n  exit 1\n  ;;\nesac\n```\n\n#### 应用状态监控\n\n| 返回值 | 含义 |\n|--------|------|\n| `exit 0` | 应用正在运行 |\n| `exit 3` | 应用未运行 |\n| `exit 1` | 执行失败 |\n\n系统会在启动前检查一次，运行期间定期轮询检查。\n\n### 8.2 Native 应用完整启动脚本\n\n```bash\n#!/bin/bash\n\nLOG_FILE=\"${TRIM_PKGVAR}/info.log\"\nPID_FILE=\"${TRIM_PKGVAR}/app.pid\"\nDATA_DIR=\"${TRIM_DATA_SHARE_PATHS%%:*}\"\nCMD=\"DATA_DIR=${DATA_DIR} PORT=5001 node ${TRIM_APPDEST}/server/server.js\"\n\nlog_msg() {\n  echo \"$(date '+%Y-%m-%d %H:%M:%S') - $1\" >> ${LOG_FILE}\n}\n\nstart_process() {\n  if status; then return 0; fi\n  log_msg \"Starting process ...\"\n  bash -c \"${CMD}\" >> ${LOG_FILE} 2>&1 &\n  printf \"%s\" \"$!\" > ${PID_FILE}\n  return 0\n}\n\nstop_process() {\n  log_msg \"Stopping process ...\"\n  if [ -r \"${PID_FILE}\" ]; then\n    pid=$(head -n 1 \"${PID_FILE}\" | tr -d '[:space:]')\n    log_msg \"pid=${pid}\"\n    if ! kill -0 \"${pid}\" 2>/dev/null; then\n      rm -f \"${PID_FILE}\"\n      return\n    fi\n    log_msg \"send TERM signal to PID:${pid}...\"\n    kill -TERM ${pid} >> ${LOG_FILE} 2>&1\n    local count=0\n    while kill -0 \"${pid}\" 2>/dev/null && [ $count -lt 10 ]; do\n      sleep 1; count=$((count + 1))\n    done\n    if kill -0 \"${pid}\" 2>/dev/null; then\n      kill -KILL \"${pid}\"\n    fi\n    rm -f \"${PID_FILE}\"\n  fi\n}\n\nstatus() {\n  if [ -f \"${PID_FILE}\" ]; then\n    pid=$(head -n 1 \"${PID_FILE}\" | tr -d '[:space:]')\n    if kill -0 \"${pid}\" 2>/dev/null; then return 0; fi\n    rm -f \"${PID_FILE}\"\n  fi\n  return 1\n}\n\ncase $1 in\n  start)  start_process ;;\n  stop)   stop_process ;;\n  status) if status; then exit 0; else exit 3; fi ;;\n  *)      exit 1 ;;\nesac\n```\n\n### 8.3 运行环境配置\n\n#### Node.js\n```bash\n# 在 cmd 脚本中配置，可选版本：nodejs_v22 / v20 / v18 / v16 / v14\nexport PATH=/var/apps/nodejs_v22/target/bin:$PATH\nnode -v\nnpm -v\n```\n\n#### Python\n```bash\n# 可选版本：python312 / 311 / 310 / 39 / 38\nexport PATH=/var/apps/python312/target/bin:$PATH\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\n```\n\n#### Java\n```bash\n# 可选版本：java-21-openjdk / java-17-openjdk / java-11-openjdk\nexport PATH=/var/apps/java-21-openjdk/target/bin:$PATH\njava --version\n```\n\n### 8.4 应用配置与文件写入\n\n飞牛 NAS 应用安装后，系统会在多个目录下为应用分配存储空间。不同目录的用途和权限差异很大，理解它们对写出健壮的应用至关重要。\n\n#### 目录一览\n\n| 目录 | 权限 | 用途 | 系统写入 | 应用写入 |\n|------|------|------|----------|----------|\n| `/vol1/@appconf/{appname}/settings.conf` | 应用用户 | **应用配置**（端口、路径等） | ✅（向导/设置页面） | ✅ |\n| `/vol1/@appdata/{appname}/` | 应用用户 | **应用数据**（运行时日志、数据库等） | ✘ | ✅ |\n| `/vol1/@apphome/{appname}/` | 应用用户 | **应用家目录** | ✘ | ✅ |\n| `/vol1/@apptemp/{appname}/` | 应用用户 | **缓存/临时文件** | ✘ | ✅ |\n| `/vol1/@appshare/{appname}/` | 应用用户 | **共享目录**（同 `TRIM_DATA_SHARE_PATHS`） | ✘ | ✅ |\n| `/vol1/@appcenter/{appname}/` | 应用用户 | **应用本身代码（只读）** | ✘（可手动修改但不建议） | ✘ |\n\n> 存储在 `/vol1/@appcenter/` 下的是应用包的运行目录（`app/server/` 等），不是 `@appconf/`。\n\n#### settings.conf：应用配置的核心\n\n`/vol1/@appconf/{appname}/settings.conf` 是 NAS 系统与应用之间的配置桥梁。它的格式为 `KEY=VALUE` 纯文本，**系统会在以下时机自动重写此文件**：\n\n1. **安装/更新**时——写入 `wizard/` 向导收集的用户输入（以 `wizard_` 为前缀的环境变量）\n2. **应用设置页面**修改时——写入授权文件夹、端口等系统级配置\n3. 应用本身也可以主动写入此文件，但要注意**系统下次重写时会覆盖**\n\n#### ⚠️ 常见陷阱：系统重写覆盖问题\n\n```\n# settings.conf 被系统重写后：\nMUSIC_PATH=/vol1/1000/Music       ← 路径被系统改写，可能与真实路径名不符\nPORT=5200\n```\n\n已知问题：\n1. **路径命名差异**——用户实际选的文件夹名（如 `music11` / `music22`）被系统写成了 `Music`（大小写不一致或截断）\n2. **授权文件夹丢失**——用户授权了多个数据目录，但系统只保存了第一个\n3. **每次修改设置都会覆盖**——手动改 `settings.conf` 后，只要用户进一次应用设置页面就会被重置\n4. **`data-share` 可能为空**——`config/resource` 中 `data-share.shares` 数组不更新，导致 `TRIM_DATA_ACCESSIBLE_PATHS` 环境变量为空\n\n#### 应对策略\n\n**策略一：应用启动时自读 settings.conf（推荐，最通用）**\n\n不依赖 `cmd/main` 传环境变量，让应用服务进程启动时**自己读取 `settings.conf`**。这样不管 NAS 系统以何种方式拉起进程（自动启动、手动重启）都能正确拿到配置。\n\n```javascript\n// Node.js — 在读取 PORT / MUSIC_PATH 之前执行\nvar path = require('path');\nvar fs = require('fs');\n\n// settings.conf 实际位置：\n//   @appconf 的挂载点 = __dirname 减去 @appcenter 后的相对路径\nvar confPath = path.resolve(__dirname, '../../../@appconf/YOUR_APP_NAME/settings.conf');\ntry {\n  var confContent = fs.readFileSync(confPath, 'utf-8');\n  var confLines = confContent.split(String.fromCharCode(10));\n  for (var i = 0; i < confLines.length; i++) {\n    var line = confLines[i].trim();\n    if (line && !line.startsWith('#')) {\n      var eqIdx = line.indexOf('=');\n      if (eqIdx > 0) {\n        var key = line.substring(0, eqIdx).trim();\n        var val = line.substring(eqIdx + 1).trim();\n        if (process.env[key] === undefined) process.env[key] = val;\n      }\n    }\n  }\n} catch(e) { /* 配置文件不存在时忽略 */ }\n\nconst PORT = process.env.PORT || 6688;      // 已被 settings.conf 注入\n```\n\n> 注意：这段代码必须放在 `PORT` 声明**之前**，否则环境变量已经读过了，兜底逻辑不会生效。\n\n```python\n# Python 版本\nimport os\n\ndef load_settings():\n    config_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))),\n                               '..', '@appconf', 'YOUR_APP_NAME', 'settings.conf')\n    # 更准确的：从 __file__ 向上走到 @appcenter，再到 @appconf\n    config_path = os.path.normpath(config_path)\n    if not os.path.exists(config_path):\n        return\n    with open(config_path, 'r') as f:\n        for line in f:\n            line = line.strip()\n            if not line or line.startswith('#'):\n                continue\n            if '=' in line:\n                key, val = line.split('=', 1)\n                key = key.strip()\n                val = val.strip()\n                if key not in os.environ:\n                    os.environ[key] = val\n\nload_settings()\nPORT = int(os.environ.get('PORT', 6688))\n```\n\n**策略二：只用 cmd/main 传环境变量（较脆弱）**\n\n```bash\n# cmd/main 中正确的做法：读 settings.conf + 传环境变量\n. /vol1/@appconf/YOUR_APP_NAME/settings.conf\nexport PORT MUSIC_PATH\nnode /vol1/@appcenter/YOUR_APP_NAME/server/server.js\n```\n\n局限：NAS 自动启动时可能不经过 `cmd/main`，环境变量仍然丢失。\n\n**策略三：验证路径有效性，失败时友好报错**\n\n```javascript\nvar musicPaths = (process.env.MUSIC_PATH || '').split(':').filter(Boolean);\nvar validPaths = [];\nmusicPaths.forEach(function(p) {\n  try {\n    if (fs.statSync(p).isDirectory()) {\n      validPaths.push(p);\n    }\n  } catch(e) {\n    console.error('Music directory not found:', p);\n  }\n});\nif (validPaths.length === 0) {\n  console.error('No valid music folder. Please set it in App Settings.');\n  process.exit(1);\n}\n```\n\n#### 实践建议\n\n1. **首选策略一**（应用自读 settings.conf）+ **策略三**（路径验证），覆盖所有启动场景\n2. 不要依赖 `TRIM_DATA_ACCESSIBLE_PATHS` 环境变量——它可能为空\n3. 对于 Native 应用，建议在 `cmd/main` 和 app 代码中都实现配置读取，双重保险\n4. 如果发现路径不匹配（如实际文件夹 `music11` 但配置写 `Music`），先确认是否是 NAS 系统写入的问题，**不要**在代码里做硬编码的路径猜测\n\n#### ⚠️ 端口变更的实战陷阱\n\n##### 问题描述\n用户在安装向导/应用设置中修改端口（`wizard_port`）后，应用重启仍然监听默认端口。\n\n##### 根因分析\n飞牛系统通过 `TRIM_SERVICE_PORT` 环境变量（来源：manifest 的 `service_port` 字段）将端口注入 `cmd/main`。但关键问题在于：\n\n- **`service_port` 只在打包时固定**，用户在安装向导或应用设置中修改端口后，`service_port` **不会自动更新**\n- `TRIM_SERVICE_PORT` 的值在首次安装后即固定，不会因 `wizard_port` 变更而重新注入\n- `config_callback` 虽然能通过 `$wizard_port` 变量拿到新端口，但直接调用 `cmd/main start` 时传给 Node 的仍然是旧的 `TRIM_SERVICE_PORT`\n\n##### 修复方案：三级端口优先级\n`cmd/main` 中端口获取应按以下优先级：\n\n```bash\n# 第一优先级：settings.conf 中持久化的 PORT（来自安装向导 / 应用设置的 wizard_port）\nif [ -f \"${TRIM_PKGETC}/settings.conf\" ]; then\n  . \"${TRIM_PKGETC}/settings.conf\"\nfi\n# 第二优先级：TRIM_SERVICE_PORT（系统注入，来自 manifest service_port）\n# 第三优先级：默认值\nPORT=\"${PORT:-${TRIM_SERVICE_PORT:-8080}}\"\n```\n\n##### 完整工作流\n1. **安装时**：`install_callback` 将 `$wizard_port` 写入 `${TRIM_PKGETC}/settings.conf`\n2. **修改端口时**（应用设置页面）：`config_callback` 将 `$wizard_port` 写入 `settings.conf`，然后重启\n3. **任何后续启动**（系统自动启动、手动重启）：`cmd/main` 优先读取 `settings.conf` 中的 `PORT`，不受 `TRIM_SERVICE_PORT` 旧值影响\n\n##### 配套的 config_callback\n```bash\nPORT=\"${wizard_port:-8080}\"\n\n# 先持久化端口到 settings.conf\nmkdir -p \"${TRIM_PKGETC}\"\ncat > \"${TRIM_PKGETC}/settings.conf\" <<EOF\nPORT=${PORT}\nEOF\n\n# 然后重启（此时不需要传 TRIM_SERVICE_PORT，因为 cmd/main 会自读 settings.conf）\nbash \"${0%/*}/main\" stop\nsleep 1\nbash \"${0%/*}/main\" start\n```\n\n##### 要点\n- **`settings.conf` 的自定义写入要放到 `install_callback` 和 `config_callback` 中**，不要在 `install_init` 或其他脚本中写，否则可能在安装流程中被系统覆盖\n- `TRIM_SERVICE_PORT` 仍然可以作为 fallback 使用，但**不要把它作为唯一的端口来源**\n- 如果 `settings.conf` 被系统重写覆盖了 `PORT`（某些系统版本已知问题），检查端口是否仍然正确，必要时在 `config_init` 中添加校验\n\n### 8.4 Docker 应用的 main 脚本\n\nDocker 应用的启停由系统通过 compose 管理，但需要定义状态检查：\n\n```bash\n#!/bin/bash\n\nFILE_PATH=\"${TRIM_APPDEST}/docker/docker-compose.yaml\"\n\nis_docker_running () {\n  DOCKER_NAME=\"\"\n  if [ -f \"$FILE_PATH\" ]; then\n    DOCKER_NAME=$(cat $FILE_PATH | grep \"container_name\" | awk -F ':' '{print $2}' | xargs)\n    echo \"DOCKER_NAME is set to: $DOCKER_NAME\"\n  fi\n  if [ -n \"$DOCKER_NAME\" ]; then\n    docker inspect $DOCKER_NAME | grep -q \"\\\"Status\\\": \\\"running\\\",\" || exit 1\n    return\n  fi\n}\n\ncase $1 in\n  start)  exit 0 ;;  # compose 管理，无需额外操作\n  stop)   exit 0 ;;  # compose 管理，无需额外操作\n  status) if is_docker_running; then exit 0; else exit 3; fi ;;\n  *)      exit 1 ;;\nesac\n```\n\n### 8.5 错误异常展示处理（V1.1.8+）\n\n向 `$TRIM_TEMP_LOGFILE` 写入错误信息并返回错误码 `1`，系统会自动以 Dialog 对话框展示给用户：\n\n```bash\n# ✅ 正确做法\necho \"配置文件不存在，应用启动失败！\" > \"${TRIM_TEMP_LOGFILE}\"\nexit 1\n\n# ❌ 错误做法\n# echo \"配置文件不存在，应用启动失败！\"\n# exit 1\n```\n\n### 8.6 生命周期事件说明\n\n| 事件 | 脚本 | 时机 |\n|------|------|------|\n| 安装前 | `install_init` | 文件解压前，可做环境检查、依赖安装 |\n| 安装后 | `install_callback` | 文件解压后，可做初始化配置 |\n| 卸载前 | `uninstall_init` | 停止应用后，卸载前 |\n| 卸载后 | `uninstall_callback` | 可清理剩余数据和目录 |\n| 升级前 | `upgrade_init` | 更新处理前 |\n| 升级后 | `upgrade_callback` | 可做数据库升级、配置迁移 |\n| 配置变更前 | `config_init` | 用户保存配置后，重启前 |\n| 配置变更后 | `config_callback` | 可监听配置变化调整运行逻辑 |\n\n#### 卸载时保留/删除数据\n系统默认保留 `var` 和 `shares` 目录。如果希望在卸载向导中让用户选择是否删除数据，在 `wizard/uninstall` 中配置选项，在 `cmd/uninstall_callback` 中根据用户选择清理：\n\n```bash\nif [ \"$wizard_data_action\" = \"delete\" ]; then\n  rm -rf \"${TRIM_PKGVAR}\" \"${TRIM_DATA_SHARE_PATHS}\"\nfi\n```\n\n---\n\n## 9. 环境变量参考\n\n### 9.1 系统环境变量\n\n| 变量 | 说明 |\n|------|------|\n| `$TRIM_APPNAME` | 应用名称 |\n| `$TRIM_APPVER` | 应用版本号 |\n| `$TRIM_APPDEST` | 应用可执行文件目录（target） |\n| `$TRIM_PKGETC` | 配置文件目录（etc） |\n| `$TRIM_PKGVAR` | 运行时数据目录（var） |\n| `$TRIM_TEMP_LOGFILE` | 临时日志文件路径（用户可见） |\n| `$TRIM_SERVICE_PORT` | 服务端口 |\n| `$TRIM_USERNAME` | 应用专用用户名 |\n| `$TRIM_RUN_USERNAME` | 当前运行用户（`root` 或应用用户） |\n| `$TRIM_DATA_SHARE_PATHS` | 数据共享目录路径列表（冒号分隔） |\n\n### 9.2 向导输入变量\n\n用户在向导中的选择会变成同名的环境变量，在脚本中可直接使用：\n\n```bash\nADMIN_USERNAME=\"$wizard_admin_username\"\nDATABASE_TYPE=\"$wizard_database_type\"\nAPP_PORT=\"$wizard_app_port\"\n```\n\n### 9.3 应用入口中的动态变量（V1.1.8+）\n\n入口配置文件 `app/ui/config` 中可使用 `${variable_name}` 动态引用向导参数：\n\n```json\n{\n  \"port\": \"${wizard_port}\",\n  \"url\": \"${wizard_path}\"\n}\n```\n\n---\n\n## 10. 向导配置 (wizard/)\n\n向导是用户与应用交互的\"引导员\"，JSON 格式，每个文件是一个 JSON 数组（多个步骤）。\n\n### 10.1 向导类型\n\n| 文件 | 用途 |\n|------|------|\n| `wizard/install` | 安装时的配置界面 |\n| `wizard/uninstall` | 卸载时的确认界面 |\n| `wizard/upgrade` | 更新时的配置界面 |\n| `wizard/config` | 设置时的配置界面 |\n\n### 10.2 向导文件结构\n\n```json\n[\n  {\n    \"stepTitle\": \"第一步标题\",\n    \"items\": [\n      { 表单项 1 },\n      { 表单项 2 }\n    ]\n  },\n  {\n    \"stepTitle\": \"第二步标题\",\n    \"items\": [\n      { 表单项 3 }\n    ]\n  }\n]\n```\n\n### 10.3 表单项类型\n\n#### text — 文本输入\n```json\n{\n  \"type\": \"text\",\n  \"field\": \"wizard_username\",\n  \"label\": \"用户名\",\n  \"initValue\": \"admin\",\n  \"rules\": [\n    { \"required\": true, \"message\": \"请输入用户名\" },\n    { \"min\": 3, \"max\": 20, \"message\": \"长度应在3-20字符之间\" }\n  ]\n}\n```\n\n#### password — 密码输入\n```json\n{\n  \"type\": \"password\",\n  \"field\": \"wizard_password\",\n  \"label\": \"管理员密码\",\n  \"rules\": [\n    { \"required\": true, \"message\": \"请输入密码\" },\n    { \"min\": 6, \"message\": \"密码长度不能少于6位\" }\n  ]\n}\n```\n\n#### radio — 单选\n```json\n{\n  \"type\": \"radio\",\n  \"field\": \"wizard_install_type\",\n  \"label\": \"安装类型\",\n  \"initValue\": \"standard\",\n  \"options\": [\n    { \"label\": \"标准安装\", \"value\": \"standard\" },\n    { \"label\": \"自定义安装\", \"value\": \"custom\" }\n  ],\n  \"rules\": [{ \"required\": true, \"message\": \"请选择安装类型\" }]\n}\n```\n\n#### checkbox — 多选\n```json\n{\n  \"type\": \"checkbox\",\n  \"field\": \"wizard_modules\",\n  \"label\": \"安装模块\",\n  \"initValue\": [\"web\", \"api\"],\n  \"options\": [\n    { \"label\": \"Web界面\", \"value\": \"web\" },\n    { \"label\": \"API接口\", \"value\": \"api\" },\n    { \"label\": \"数据库\", \"value\": \"database\" }\n  ],\n  \"rules\": [{ \"required\": true, \"message\": \"请至少选择一个模块\" }]\n}\n```\n\n#### select — 下拉选择\n```json\n{\n  \"type\": \"select\",\n  \"field\": \"wizard_database_type\",\n  \"label\": \"数据库类型\",\n  \"initValue\": \"sqlite\",\n  \"options\": [\n    { \"label\": \"SQLite (推荐)\", \"value\": \"sqlite\" },\n    { \"label\": \"MySQL\", \"value\": \"mysql\" },\n    { \"label\": \"PostgreSQL\", \"value\": \"postgresql\" }\n  ],\n  \"rules\": [{ \"required\": true, \"message\": \"请选择数据库类型\" }]\n}\n```\n\n#### switch — 开关\n```json\n{\n  \"type\": \"switch\",\n  \"field\": \"wizard_enable_backup\",\n  \"label\": \"启用自动备份\",\n  \"initValue\": \"true\"\n}\n```\n\n#### tips — 提示文本（不收集输入）\n```json\n{\n  \"type\": \"tips\",\n  \"helpText\": \"请阅读 <a target=\\\"_blank\\\" href=\\\"https://example.com/privacy\\\">隐私政策</a>。\"\n}\n```\n\n### 10.4 验证规则\n\n| 规则 | 示例 |\n|------|------|\n| 必填 | {\\\"required\\\": true, \\\"message\\\": \\\"此字段不能为空\\\"} |\n| 最小长度 | {\\\"min\\\": 3, \\\"message\\\": \\\"长度不能少于3\\\"} |\n| 最大长度 | {\\\"max\\\": 50, \\\"message\\\": \\\"长度不能超过50\\\"} |\n| 精确长度 | {\\\"len\\\": 6, \\\"message\\\": \\\"请输入6位验证码\\\"} |\n| 正则 | {\\\"pattern\\\": \\\"^[a-zA-Z0-9_]+$\\\", \\\"message\\\": \\\"只能含字母数字下划线\\\"} |\n\n### 10.5 安装向导完整示例\n\n```json\n[\n  {\n    \\\"stepTitle\\\": \\\"欢迎安装\\\",\n    \\\"items\\\": [\n      {\n        \\\"type\\\": \\\"tips\\\",\n        \\\"helpText\\\": \\\"欢迎使用我们的应用！在开始使用前，请阅读并同意我们的服务条款。\\\"\n      },\n      {\n        \\\"type\\\": \\\"switch\\\",\n        \\\"field\\\": \\\"wizard_agree_terms\\\",\n        \\\"label\\\": \\\"我已阅读并同意服务条款\\\",\n        \\\"rules\\\": [\n          { \\\"required\\\": true, \\\"message\\\": \\\"请同意服务条款\\\" }\n        ]\n      }\n    ]\n  },\n  {\n    \\\"stepTitle\\\": \\\"创建管理员账号\\\",\n    \\\"items\\\": [\n      {\n        \\\"type\\\": \\\"text\\\",\n        \\\"field\\\": \\\"wizard_admin_username\\\",\n        \\\"label\\\": \\\"管理员用户名\\\",\n        \\\"initValue\\\": \\\"admin\\\",\n        \\\"rules\\\": [\n          { \\\"required\\\": true, \\\"message\\\": \\\"请输入管理员用户名\\\" },\n          { \\\"pattern\\\": \\\"^[a-zA-Z0-9_]+$\\\", \\\"message\\\": \\\"只能包含字母、数字和下划线\\\" }\n        ]\n      },\n      {\n        \\\"type\\\": \\\"password\\\",\n        \\\"field\\\": \\\"wizard_admin_password\\\",\n        \\\"label\\\": \\\"管理员密码\\\",\n        \\\"rules\\\": [\n          { \\\"required\\\": true, \\\"message\\\": \\\"请输入密码\\\" },\n          { \\\"min\\\": 8, \\\"message\\\": \\\"密码长度不能少于8位\\\" }\n        ]\n      },\n      {\n        \\\"type\\\": \\\"password\\\",\n        \\\"field\\\": \\\"wizard_admin_password_confirm\\\",\n        \\\"label\\\": \\\"确认密码\\\",\n        \\\"rules\\\": [\n          { \\\"required\\\": true, \\\"message\\\": \\\"请确认密码\\\" }\n        ]\n      }\n    ]\n  },\n  {\n    \\\"stepTitle\\\": \\\"应用配置\\\",\n    \\\"items\\\": [\n      {\n        \\\"type\\\": \\\"select\\\",\n        \\\"field\\\": \\\"wizard_database_type\\\",\n        \\\"label\\\": \\\"数据库类型\\\",\n        \\\"initValue\\\": \\\"sqlite\\\",\n        \\\"options\\\": [\n          { \\\"label\\\": \\\"SQLite (推荐，无需额外配置)\\\", \\\"value\\\": \\\"sqlite\\\" },\n          { \\\"label\\\": \\\"MySQL\\\", \\\"value\\\": \\\"mysql\\\" }\n        ]\n      },\n      {\n        \\\"type\\\": \\\"text\\\",\n        \\\"field\\\": \\\"wizard_app_port\\\",\n        \\\"label\\\": \\\"应用端口\\\",\n        \\\"initValue\\\": \\\"8080\\\",\n        \\\"rules\\\": [\n          { \\\"required\\\": true, \\\"message\\\": \\\"请输入端口号\\\" },\n          { \\\"pattern\\\": \\\"^[0-9]+$\\\", \\\"message\\\": \\\"端口号必须是数字\\\" }\n        ]\n      }\n    ]\n  }\n]\n```\n\n### 10.6 卸载向导示例\n\n```json\n[\n  {\n    \\\"stepTitle\\\": \\\"确认卸载\\\",\n    \\\"items\\\": [\n      {\n        \\\"type\\\": \\\"tips\\\",\n        \\\"helpText\\\": \\\"您即将卸载此应用。请选择如何处理应用数据：\\\"\n      },\n      {\n        \\\"type\\\": \\\"radio\\\",\n        \\\"field\\\": \\\"wizard_data_action\\\",\n        \\\"label\\\": \\\"数据保留选项\\\",\n        \\\"initValue\\\": \\\"keep\\\",\n        \\\"options\\\": [\n          { \\\"label\\\": \\\"保留数据（推荐）- 将来重新安装时可恢复\\\", \\\"value\\\": \\\"keep\\\" },\n          { \\\"label\\\": \\\"删除所有数据 - 此操作不可恢复！\\\", \\\"value\\\": \\\"delete\\\" }\n        ],\n        \\\"rules\\\": [\n          { \\\"required\\\": true, \\\"message\\\": \\\"请选择数据保留选项\\\" }\n        ]\n      },\n      {\n        \\\"type\\\": \\\"tips\\\",\n        \\\"helpText\\\": \\\"<strong>警告：</strong> 选择删除数据后，所有应用数据将永久丢失，无法恢复。\\\"\n      }\n    ]\n  }\n]\n```\n\n### 10.7 获取用户输入\n\n```bash\n# 用户输入直接作为环境变量使用\nADMIN_USERNAME=\\\"\\$wizard_admin_username\\\"\nADMIN_PASSWORD=\\\"\\$wizard_admin_password\\\"\nDATABASE_TYPE=\\\"\\$wizard_database_type\\\"\nAPP_PORT=\\\"\\$wizard_app_port\\\"\n```\n\n## 11. 图标规范\n\n### 11.1 包文件图标（根目录）\n\n| 文件 | 尺寸 | 格式 | 必填 |\n|------|------|------|------|\n| `ICON.PNG` | 64×64 像素 | PNG，不透明 | ✅ |\n| `ICON_256.PNG` | 256×256 像素 | PNG，不透明 | ✅ |\n\n> 含圆角矩形背景的 PSD 源文件可下载：https://static.fnnas.com/appcenter-marketing/fnpack_ICON_256.zip\n\n### 11.2 UI 入口图标（app/ui/images/）\n\n| 文件 | 尺寸 | 命名规则 |\n|------|------|----------|\n| `icon_64.png` | 64×64 | **小写**，配置中 `{0}` 替换为尺寸 |\n| `icon_256.png` | 256×256 | 如 `images/icon_{0}.png` → 自动选取 |\n\n---\n\n## 12. Docker 应用构建详解\n\n### 12.1 创建项目\n\n```bash\nfnpack create my-app --template docker\n```\n\n### 12.2 目录结构\n\n```\nmy-app/\n├── app/\n│   ├── docker/\n│   │   └── docker-compose.yaml\n│   └── ui/\n│       ├── images/\n│       └── config\n├── manifest\n├── cmd/\n├── config/\n│   ├── privilege\n│   └── resource\n├── wizard/\n├── LICENSE\n├── ICON.PNG\n└── ICON_256.PNG\n```\n\n### 12.3 docker-compose.yaml 示例\n\n```yaml\nversion: '3.8'\n\nservices:\n  web:\n    image: myapp:latest\n    container_name: myapp-web\n    ports:\n      - \"${TRIM_SERVICE_PORT}:80\"\n    volumes:\n      - \"${TRIM_PKGVAR}:/app/data\"\n      - \"${TRIM_DATA_SHARE_PATHS}:/app/shares\"\n    environment:\n      - DB_HOST=db\n      - APP_PORT=${TRIM_SERVICE_PORT}\n\n  db:\n    image: mysql:8.0\n    container_name: myapp-db\n    environment:\n      - MYSQL_ROOT_PASSWORD=${wizard_db_password}\n      - MYSQL_DATABASE=myapp\n    volumes:\n      - db_data:/var/lib/mysql\n\nvolumes:\n  db_data:\n```\n\n> `docker-compose.yaml` 支持使用全部系统环境变量和向导变量。\n> 系统统一管理 compose 的启动/停止，`cmd/main` 只需要实现 status 检查。\n\n### 12.4 资源配置\n\n```json\n{\n  \"docker-project\": {\n    \"projects\": [\n      {\n        \"name\": \"myapp-stack\",\n        \"path\": \"docker\"\n      }\n    ]\n  }\n}\n```\n\n---\n\n## 13. 统一网关（fnOS V1.1.3100+）\n\n统一网关为应用提供稳定的访问入口，无需新增端口监听。HTTP 和 WebSocket 均可接入。\n\n### 13.1 接入方式\n\n在 `app/ui/config` 中声明 `gatewayPrefix` 和 `gatewaySocket`：\n\n```json\n{\n  \".url\": {\n    \"myapp.main\": {\n      \"title\": \"我的应用\",\n      \"icon\": \"images/icon_{0}.png\",\n      \"type\": \"iframe\",\n      \"protocol\": \"\",\n      \"gatewaySocket\": \"app.sock\",\n      \"gatewayPrefix\": \"/app/myapp\",\n      \"url\": \"/app/myapp\",\n      \"allUsers\": true\n    }\n  }\n}\n```\n\n#### 字段说明\n| 字段 | 条件 | 说明 |\n|------|------|------|\n| `gatewayPrefix` | 两者均非空时注册 | 网关访问前缀，格式 `/app/{appname}/{customPath}` 或 `/app/{appname}`，不能包含 `.` |\n| `gatewaySocket` | 两者均非空时注册 | Socket 文件名，如 `app.sock`，放在应用 target 目录 |\n\n### 13.2 WebSocket 支持\n\n```json\n{\n  \"myapp.chat\": {\n    \"title\": \"聊天应用\",\n    \"icon\": \"images/icon_{0}.png\",\n    \"type\": \"iframe\",\n    \"protocol\": \"\",\n    \"gatewaySocket\": \"chat.sock\",\n    \"gatewayPrefix\": \"/app/chat\",\n    \"url\": \"/app/chat\",\n    \"allUsers\": true\n  }\n}\n```\n\n前端连接示例：\n```javascript\nconst wsProtocol = window.location.protocol === \"https:\" ? \"wss:\" : \"ws:\";\nconst wsUrl = `${wsProtocol}//${window.location.host}/app/chat/ws`;\nconst socket = new WebSocket(wsUrl);\n```\n\n> WebSocket 路由建议固定为网关前缀下的子路径（如 `/ws`），不要信任客户端主动上报的用户 ID。\n\n### 13.3 登录认证\n\n统一网关转发请求前完成登录态校验，认证通过后增加以下 Header：\n\n| Header | 说明 | 示例 |\n|--------|------|------|\n| `X-Trim-Userid` | 当前登录用户 UID | `1000` |\n| `X-Trim-Isadmin` | 是否管理员 | `true` / `false` |\n| `X-Trim-Username` | 当前登录用户名 | `admin` |\n\nNode.js 获取用户信息：\n```javascript\nfunction getGatewayUser(req) {\n  return {\n    uid: req.headers[\"x-trim-userid\"],\n    isAdmin: req.headers[\"x-trim-isadmin\"] === \"true\",\n    username: req.headers[\"x-trim-username\"]\n  };\n}\n```\n\n> 应用仍需要自己的权限判断逻辑（数据隔离、管理员接口、高风险操作校验）。\n\n### 13.4 不鉴权接口\n\n公开资源、OAuth 回调等无需登录态的特殊接口：\n- 只开放必要路径\n- 只允许必要 HTTP 方法\n- 不返回用户敏感信息\n- 不提供写入/删除等高危能力\n\n### 13.5 静态文件安全\n- 路径标准化处理\n- 禁止 `..` 访问上级目录\n- 限制可访问目录范围\n- 不暴露配置文件、密钥、数据库\n- 可下载文件类型做白名单控制\n\n---\n\n## 14. 运行时环境与中间件\n\n### 14.1 运行时环境\n\n通过 `manifest` 的 `install_dep_apps` 声明依赖，系统确保安装和启动时目标环境已就绪。\n\n| 运行时 | manifest 声明 | PATH 配置 |\n|--------|---------------|-----------|\n| Node.js v22 | `install_dep_apps=nodejs_v22` | `export PATH=/var/apps/nodejs_v22/target/bin:$PATH` |\n| Node.js v20 | `install_dep_apps=nodejs_v20` | `export PATH=/var/apps/nodejs_v20/target/bin:$PATH` |\n| Node.js v18 | `install_dep_apps=nodejs_v18` | `export PATH=/var/apps/nodejs_v18/target/bin:$PATH` |\n| Python 3.12 | `install_dep_apps=python312` | `export PATH=/var/apps/python312/target/bin:$PATH` |\n| Python 3.11 | `install_dep_apps=python311` | `export PATH=/var/apps/python311/target/bin:$PATH` |\n| Python 3.10 | `install_dep_apps=python310` | `export PATH=/var/apps/python310/target/bin:$PATH` |\n| Java 21 | `install_dep_apps=java-21-openjdk` | `export PATH=/var/apps/java-21-openjdk/target/bin:$PATH` |\n| Java 17 | `install_dep_apps=java-17-openjdk` | `export PATH=/var/apps/java-17-openjdk/target/bin:$PATH` |\n\n**Python 虚拟环境最佳实践：**\n```bash\nexport PATH=/var/apps/python312/target/bin:$PATH\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\n```\n\n### 14.2 中间件服务\n\n| 中间件 | manifest 声明 | 连接信息 |\n|--------|---------------|----------|\n| Redis | `install_dep_apps=redis` | `127.0.0.1:6379` |\n| MinIO | `install_dep_apps=minio` | `127.0.0.1:9000` |\n| RabbitMQ | `install_dep_apps=rabbitmq` | `127.0.0.1:5672`，默认 `guest/guest` |\n| MariaDB | `install_dep_apps=mariaDB` | 即将上线 |\n\n#### Redis 使用示例（Python）\n```python\nimport redis\n\npool = redis.ConnectionPool(\n    host='127.0.0.1', port=6379, db=1,\n    decode_responses=True, max_connections=10\n)\nclient = redis.Redis(connection_pool=pool)\nclient.lpush('my_list', 'item1', 'item2')\nitems = client.lrange('my_list', 0, -1)\n```\n\n#### MinIO 使用示例（Python）\n```python\nfrom minio import Minio\n\nclient = Minio(\n    endpoint=\"127.0.0.1:9000\",\n    access_key=\"your_access_key\",\n    secret_key=\"your_secret_key\",\n    secure=False\n)\nif not client.bucket_exists(\"my-bucket\"):\n    client.make_bucket(\"my-bucket\")\n```\n\n#### RabbitMQ 使用示例（Python）\n```python\nimport pika\n\nconnection = pika.BlockingConnection(pika.ConnectionParameters(\n    host=\"127.0.0.1\", port=5672,\n    virtual_host=\"/\",\n    credentials=pika.PlainCredentials(\"guest\", \"guest\")\n))\nchannel = connection.channel()\nchannel.queue_declare(queue=\"my_queue\")\nchannel.basic_publish(exchange=\"\", routing_key=\"my_queue\", body=\"Hello\")\n```\n\n---\n\n## 15. 应用依赖关系\n\n### 15.1 声明依赖\n\n在 `manifest` 中使用 `install_dep_apps` 字段：\n\n```ini\n# 格式：app1>2.2.2:app2:app3\n# > 表示最低版本要求\n# : 分隔多个依赖\ninstall_dep_apps = dep2:dep1\n```\n\n### 15.2 依赖检查逻辑\n\n| 操作 | 行为 |\n|------|------|\n| 安装/启用 | 检查依赖是否已安装启用，未安装则自动安装，未启用则自动启用 |\n| 停用/卸载 | 检查是否有其他应用依赖本应用，有则提示自动停用 |\n| 更新 | 检查是否有其他应用依赖，有则在更新期间自动停用 |\n\n### 15.3 依赖顺序\n\n自动安装和启用的顺序**从后往前**：\n```ini\n# 先安装 dep1，后安装 dep2\ninstall_dep_apps = dep2:dep1\n```\n\n### 15.4 嵌套依赖\n\n> 应用中心仅对一层依赖进行检查，不做递归检查。\n\n如果 A 依赖 B，B 依赖 C，则 A 需要同时声明 B 和 C：\n```ini\ninstall_dep_apps = depB:depC\n```\n\n---\n\n## 16. Native 应用完整构建流程\n\n### 16.1 开发 → 打包流程\n\n1. 开发应用代码（如 Node.js + Express）\n2. 编译打包到 `dist/` 目录\n3. `fnpack create <appname>` 创建打包目录\n4. 将编译产物复制到 `app/server/`\n5. 编辑 `manifest`、`config/privilege`、`config/resource`\n6. 编写 `cmd/main` 生命周期脚本\n7. 配置 `app/ui/config` 入口\n8. 添加 `app/ui/images/icon_64.png` 和 `icon_256.png`\n9. 更新根目录 `ICON.PNG` 和 `ICON_256.PNG`\n10. `fnpack build` 打包\n\n### 16.2 集成到编译脚本\n\n```javascript\n// scripts/build-combined.js（Node.js 项目）\nconst packDir = path.join(root, 'fnnas.notepad')\nconst packServerDir = path.join(packDir, 'app', 'server');\nrun(`rm -rf ${packServerDir}`);\nrun(`mkdir ${packServerDir}`);\nrun(`cp -r ${outDir}/* ${packServerDir}/`);\nrun(`fnpack build -d ${packDir}`);\n```\n\n### 16.3 纯静态页面 Native 应用\n\n对于无后台服务的静态页面应用，`cmd/main` 只需：\n```bash\n#!/bin/bash\n\ncase $1 in\nstart)   exit 0 ;;  # 无进程需要启动\nstop)    exit 0 ;;  # 无进程需要停止\nstatus)  exit 0 ;;  # 静态页面始终\"运行中\"\n*)       exit 1 ;;\nesac\n```\n\n> 此时的 CGI 脚本负责处理 HTTP 请求，从 `app/www/` 中读取并返回静态文件。\n\n---\n\n## 17. 打包与校验\n\n### 17.1 基本打包\n\n```bash\ncd myapp\nfnpack build                    # 当前目录打包\nfnpack build --directory <path> # 指定目录打包\n```\n\n### 17.2 打包校验规则\n\n| 路径 | 校验要求 |\n|------|----------|\n| `manifest` | 必须存在，必选字段存在 |\n| `config/privilege` | 必须存在，符合 JSON 格式 |\n| `config/resource` | 必须存在，符合 JSON 格式 |\n| `ICON.PNG` | 必须存在 |\n| `ICON_256.PNG` | 必须存在 |\n| `app/` | 目录必须存在 |\n| `cmd/` | 目录必须存在 |\n| `wizard/` | 目录必须存在 |\n| `app/{desktop_uidir}/` | 若 manifest 定义，目录必须存在 |\n\n### 17.3 输出\n\n打包后在当前目录生成 `{appname}.fpk` 文件。\n\n---\n\n## 18. 测试安装\n\n### 方式一：install-fpk（上传后安装）\n\n```bash\n# 将 fpk 文件上传到 fnOS 设备\nappcenter-cli install-fpk myapp.fpk\n\n# 静默安装（跳过向导）\nappcenter-cli install-fpk myapp.fpk --env config.env\n```\n\n环境变量文件 `config.env` 格式：\n```ini\n# 应用配置\nwizard_admin_username=admin\nwizard_admin_password=mypassword123\nwizard_database_type=sqlite\nwizard_app_port=8080\nwizard_agree_terms=true\n```\n\n### 方式二：install-local（开发测试）\n\n```bash\n# 在应用目录中直接安装，无需打包\ncd /path/to/myapp\nappcenter-cli install-local\n```\n\n### 检查日志\n\n日志位置：`/var/apps/{appname}/var/info.log`\n\n---\n\n## 19. 上架发布\n\n开发者后台即将上线。当前可通过加入**应用中心开发者先锋交流群**，联系专员办理应用内测和上架。\n\n---\n\n## 20. 文档更新历史\n\n| 版本 | 日期 | 主要内容 |\n|------|------|----------|\n| 20251216 | 2025-12-16 | manifest 新增 changelog；fnpack 更新至 1.0.4；新增搜索；优化创建应用教学案例（HelloFnosAppCenter） |\n| 20251231 | 2025-12-31 | 新增 New!/Update! 徽标；arch 废弃，platform 字段替代；入口配置支持环境变量；protocol 支持空字符串（自适应）；fnpack 1.2.0（新增 Linux ARM，补全校验错误处理）；新增错误异常展示处理（$TRIM_TEMP_LOGFILE）；框架文档结构调整 |\n| 20260509 | 2026-05-09 | 新增统一网关注册文档；新增登录认证文档（fnOS V1.1.3100+） |\n\n---\n\n## 附录：命令行速查\n\n```bash\n# === fnpack 打包 ===\nfnpack create <appname>                                    # 创建 Native 项目\nfnpack create <appname> --template docker                  # 创建 Docker 项目\nfnpack create <appname> --without-ui true                  # 纯服务项目\nfnpack build                                               # 打包\nfnpack build --directory <path>                            # 指定目录打包\n\n# === appcenter-cli 管理 ===\nappcenter-cli install-fpk <file.fpk>                       # 安装 fpk\nappcenter-cli install-fpk <file.fpk> --env config.env      # 静默安装\nappcenter-cli install-local                                # 本地目录安装\nappcenter-cli list                                         # 已安装列表\nappcenter-cli start <appname>                              # 启动\nappcenter-cli stop <appname>                               # 停止\nappcenter-cli manual-install [enable|disable]              # 手动安装开关\nappcenter-cli default-volume [number]                      # 默认存储空间\n```\n\nFile v1.3.0:_meta.json\n\n{\n  \"ownerId\": \"kn77pvxj7zf4hrd6q5ydgn2js5826rny\",\n  \"slug\": \"fn-fpk\",\n  \"version\": \"1.3.0\",\n  \"publishedAt\": 1780470444023\n}\n\nFile v1.3.0:skill-card.md\n\n## Description: <br>\nFn Fpk helps developers build and package third-party fnOS FPK applications for Feiniu NAS, covering native and Docker app structure, manifests, permissions, lifecycle scripts, runtime services, packaging, testing, and publishing. <br>\n\nThis skill is ready for commercial/non-commercial use. <br>\n\n## Publisher: <br>\n[dalingo81](https://clawhub.ai/user/dalingo81) <br>\n\n### License/Terms of Use: <br>\nMIT-0 <br>\n\n\n## Use Case: <br>\nDevelopers and engineers use this skill to create, configure, package, test, and prepare third-party fnOS FPK applications for Feiniu NAS. It supports both native applications and Docker-based applications with guidance for manifests, permissions, lifecycle scripts, UI entries, middleware, and appcenter-cli installation testing. <br>\n\n### Deployment Geography for Use: <br>\nGlobal <br>\n\n## Known Risks and Mitigations: <br>\nRisk: FPK projects created from the guidance can run with significant authority on a NAS, including privileged modes, shared-folder permissions, uninstall-time data deletion, Docker environment variables, and stored passwords. <br>\nMitigation: Review generated FPK projects before installing them, with particular attention to privilege mode, shared-folder access, uninstall behavior, Docker environment variables, and any stored credentials. <br>\n\n\n## Reference(s): <br>\n- [ClawHub Fn Fpk release page](https://clawhub.ai/dalingo81/fn-fpk) <br>\n- [fnOS developer documentation](https://developer.fnnas.com) <br>\n- [fnpack 1.2.1 download pattern](https://static2.fnnas.com/fnpack/fnpack-1.2.1-{os}-{arch}) <br>\n- [FNOSP CGI collection](https://github.com/FNOSP/fnosAppCenterCgiCollection) <br>\n- [fnpack icon package](https://static.fnnas.com/appcenter-marketing/fnpack_ICON_256.zip) <br>\n\n\n## Skill Output: <br>\n**Output Type(s):** [Markdown, Code, Shell commands, Configuration guidance] <br>\n**Output Format:** [Markdown with inline shell, JSON, INI, YAML, and code examples] <br>\n**Output Parameters:** [1D] <br>\n**Other Properties Related to Output:** [Documentation-only guidance; generated FPK projects should be reviewed before installation on a NAS.] <br>\n\n## Skill Version(s): <br>\n1.3.0 (source: ClawHub release evidence, changelog dated 2026-06-02) <br>\n\n## Ethical Considerations: <br>\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment. <br>\n\nArchive v1.2.0: 3 files, 17021 bytes\n\nFiles: skill-card.md (2522b), SKILL.md (44317b), _meta.json (125b)\n\nFile v1.2.0:SKILL.md\n\n---\nname: fn-fpk\ndescription: 飞牛NAS (fnOS) FPK 应用打包开发技能。使用此技能开发和打包飞牛NAS第三方应用（.fpk），包括：Native 应用（Node.js/Python/Java/Go/Shell 等）和 Docker 应用。涵盖整个开发周期：开发环境准备、fnpack 创建项目、manifest 配置、权限/资源配置、用户入口配置（应用入口 app/ui/config + 桌面图标 + 文件右键菜单）、生命周期脚本编写（cmd/main）、向导配置（wizard）、图标规范、CGI 反向代理、统一网关注册/认证、运行时环境（Python/Node.js/Java）、中间件服务（Redis/MinIO/RabbitMQ/MariaDB）、依赖管理、fnpack CLI 打包、appcenter-cli 测试安装、到上架发布。用户提到\"飞牛\"、\"fnOS\"、\"FPK\"、\"飞牛应用\"等关键词时触发。\n---\n\n# fn-fpk — 飞牛 NAS fnOS FPK 应用开发\n\n> 基于官方文档 https://developer.fnnas.com 于 2026-06-02 全面更新。\n> 系统架构: x86_64, Linux 内核 6.12.18+, Debian 发行版。\n> fnpack 版本: 1.2.1, appcenter-cli 预装在 fnOS 中。\n\n---\n\n## 1. 开发环境准备\n\n### 1.1 系统要求\n- **fnOS 版本**: ≥ 0.9.27\n- **架构**: 仅支持 x86_64 (AMD64) — 应用的编译选项也需选择 x86_64\n- **存储**: 至少创建一个存储空间\n- **权限**: 管理员权限（安装/卸载/系统设置）\n\n### 1.2 开发方式\n- **本地开发**: 在本地用 fnpack CLI 打包，再传送到 fnOS 设备安装\n- **远程开发**: SSH 到 fnOS 设备直接开发，结合 `appcenter-cli install-local` 快速测试\n\n### 1.3 技术栈\n| 语言/框架 | 支持情况 |\n|-----------|---------|\n| Node.js | ✅ (v14/v16/v18/v20/v22) |\n| Python | ✅ (3.8/3.9/3.10/3.11/3.12) |\n| Java | ✅ (11/17/21 OpenJDK) |\n| Go | ✅ Linux 运行时支持 |\n| Shell | ✅ (Bash) |\n| HTML/JS/CSS | ✅ 前端任意框架 |\n\n### 1.4 CLI 工具\n\n#### fnpack 打包工具\n下载地址：https://static2.fnnas.com/fnpack/fnpack-1.2.1-{os}-{arch}\n\n| 平台 | 二进制 |\n|------|--------|\n| Windows x86 | `fnpack-1.2.1-windows-amd64` |\n| Linux x86 | `fnpack-1.2.1-linux-amd64` |\n| Linux ARM | `fnpack-1.2.1-linux-arm64` |\n| macOS Intel | `fnpack-1.2.1-darwin-amd64` |\n| macOS M 系列 | `fnpack-1.2.1-darwin-arm64` |\n\n安装：\n```bash\nchmod +x fnpack-1.2.1-linux-amd64\nsudo mv fnpack-1.2.1-linux-amd64 /usr/local/bin/fnpack\nfnpack --help\n```\n\n#### appcenter-cli （预装在 fnOS 中）\n```bash\n# 安装 fpk 文件\nappcenter-cli install-fpk myapp.fpk\n\n# 从本地目录安装（开发模式，无需打包）\ncd /path/to/myapp\nappcenter-cli install-local\n\n# 管理应用\nappcenter-cli list              # 已安装列表\nappcenter-cli start myapp       # 启动\nappcenter-cli stop myapp        # 停止\n\n# 手动安装功能（仅测试用途）\nappcenter-cli manual-install             # 查看状态\nappcenter-cli manual-install enable      # 开启\nappcenter-cli manual-install disable     # 关闭\n\n# 设置默认存储空间\nappcenter-cli default-volume             # 查看当前\nappcenter-cli default-volume 1           # 设置存储空间1\n\n# 静默安装（跳过向导）\nappcenter-cli install-fpk myapp.fpk --env config.env\n```\n\n---\n\n## 2. 应用类型\n\n| 类型 | 描述 | 创建命令 |\n|------|------|----------|\n| **Native 应用** | 直接运行在 fnOS 上的应用 | `fnpack create <appname>` |\n| **Docker 应用** | 基于 Docker Compose 容器编排 | `fnpack create <appname> --template docker` |\n| **纯服务应用**（无 UI） | 无 Web 访问入口 | 加 `--without-ui true` |\n\n---\n\n## 3. 项目结构与核心文件\n\n### 3.1 通用结构（fnpack create 生成）\n\n```\nmyapp/\n├── app/                      # 应用可执行文件/资源目录\n│   ├── server/               # 后台服务程序（Native 应用）\n│   ├── ui/                   # Web UI 入口配置\n│   │   ├── images/           # 入口图标（icon_64.png, icon_256.png）\n│   │   └── config            # 入口配置文件（JSON）\n│   ├── www/                  # Web 静态资源（HTML/CSS/JS）\n│   └── docker/               # Docker Compose 文件（Docker 应用）\n│       └── docker-compose.yaml\n├── manifest                  # 应用基本信息（必需）\n├── cmd/                      # 生命周期管理脚本（全部必需）\n│   ├── main                  # 启动/停止/状态检查\n│   ├── install_init          # 安装前初始化\n│   ├── install_callback      # 安装后回调\n│   ├── uninstall_init        # 卸载前\n│   ├── uninstall_callback    # 卸载后\n│   ├── upgrade_init          # 升级前\n│   ├── upgrade_callback      # 升级后\n│   ├── config_init           # 配置变更前\n│   └── config_callback       # 配置变更后\n├── config/\n│   ├── privilege             # 权限配置（JSON，必需）\n│   └── resource              # 资源配置（JSON，必需）\n├── wizard/                   # 向导配置（可选）\n│   ├── install               # 安装向导\n│   ├── uninstall             # 卸载向导\n│   ├── upgrade               # 更新向导\n│   └── config                # 配置向导\n├── ICON.PNG                  # 64×64 应用图标（必需）\n├── ICON_256.PNG              # 256×256 应用图标（必需）\n└── LICENSE                   # 许可证（可选）\n```\n\n### 3.2 安装后的目录结构\n\n当应用安装到 fnOS 后，系统创建如下目录：\n\n```\n/var/apps/[appname]/\n├── cmd/                      # 生命周期脚本（来自包）\n├── config/\n│   ├── privilege             # 权限配置\n│   └── resource              # 资源配置\n├── ICON_256.PNG\n├── ICON.PNG\n├── LICENSE\n├── manifest\n├── etc -> /vol{volume}/@appconf/[appname]     # 静态配置文件\n├── home -> /vol{volume}/@apphome/[appname]    # 用户数据\n├── target -> /vol{volume}/@appcenter/[appname] # 可执行文件\n├── tmp -> /vol{volume}/@apptemp/[appname]     # 临时文件\n├── var -> /vol{volume}/@appdata/[appname]     # 运行时数据\n├── shares/                   # 共享数据目录（按 resource 配置）\n│   ├── datashare1 -> /v\n\nArchive v1.0.0: 3 files, 22743 bytes\n\nFiles: skill-card.md (2368b), SKILL.md (58252b), _meta.json (125b)","readmeExcerpt":"Skill: fn-fpk Owner: dalingo81 Summary: 飞牛NAS (fnOS) FPK 应用打包开发技能。使用此技能开发和打包飞牛NAS第三方应用（.fpk），包括：Native 应用（Node.js/Python/Java/Go/Shell 等）和 Docker 应用。涵盖整个开发周期：开发环境准备、fnpack 创建项目、manifest 配置、权限/资源配置、用户入口配置（应用入口 app/ui/config + 桌面图标 + 文件右键菜单）、生命周期脚本编写（cmd/main）、向导配置（wizard）、图标规范、CGI 反向代理、统一网关注册/认证、运行时环境（Python/Node.js/Java）、中间件服务（Redis/MinIO/RabbitMQ/MariaDB）、依赖管理、fnpack CLI 打包、appcenter-cli 测试安装、到上架发布；以及飞牛开放平台 Open API","codeSnippets":[],"executableExamples":[{"language":"bash","snippet":"chmod +x fnpack-1.2.3-linux-amd64\nsudo mv fnpack-1.2.3-linux-amd64 /usr/local/bin/fnpack\nfnpack --help"},{"language":"bash","snippet":"# 安装 fpk 文件\nappcenter-cli install-fpk myapp.fpk\n\n# 从本地目录安装（开发模式，无需打包）\ncd /path/to/myapp\nappcenter-cli install-local\n\n# 管理应用\nappcenter-cli list              # 已安装列表\nappcenter-cli start myapp       # 启动\nappcenter-cli stop myapp        # 停止\n\n# 手动安装功能（仅测试用途）\nappcenter-cli manual-install             # 查看状态\nappcenter-cli manual-install enable      # 开启\nappcenter-cli manual-install disable     # 关闭\n\n# 设置默认存储空间\nappcenter-cli default-volume             # 查看当前\nappcenter-cli default-volume 1           # 设置存储空间1\n\n# 静默安装（跳过向导）\nappcenter-cli install-fpk myapp.fpk --env config.env"},{"language":"text","snippet":"myapp/\n├── app/                      # 应用可执行文件/资源目录\n│   ├── server/               # 后台服务程序（Native 应用）\n│   ├── ui/                   # Web UI 入口配置\n│   │   ├── images/           # 入口图标（icon_64.png, icon_256.png）\n│   │   └── config            # 入口配置文件（JSON）\n│   ├── www/                  # Web 静态资源（HTML/CSS/JS）\n│   └── docker/               # Docker Compose 文件（Docker 应用）\n│       └── docker-compose.yaml\n├── manifest                  # 应用基本信息（必需）\n├── cmd/                      # 生命周期管理脚本（全部必需）\n│   ├── main                  # 启动/停止/状态检查\n│   ├── install_init          # 安装前初始化\n│   ├── install_callback      # 安装后回调\n│   ├── uninstall_init        # 卸载前\n│   ├── uninstall_callback    # 卸载后\n│   ├── upgrade_init          # 升级前\n│   ├── upgrade_callback      # 升级后\n│   ├── config_init           # 配置变更前\n│   └── config_callback       # 配置变更后\n├── config/\n│   ├── privilege             # 权限配置（JSON，必需）\n│   └── resource              # 资源配置（JSON，必需）\n├── wizard/                   # 向导配置（可选）\n│   ├── install               # 安装向导\n│   ├── uninstall             # 卸载向导\n│   ├── upgrade               # 更新向导\n│   └── config                # 配置向导\n├── ICON.PNG                  # 64×64 应用图标（必需）\n├── ICON_256.PNG              # 256×256 应用图标（必需）\n└── LICENSE                   # 许可证（可选）"},{"language":"text","snippet":"/var/apps/[appname]/\n├── cmd/                      # 生命周期脚本（来自包）\n├── config/\n│   ├── privilege             # 权限配置\n│   └── resource              # 资源配置\n├── ICON_256.PNG\n├── ICON.PNG\n├── LICENSE\n├── manifest\n├── etc -> /vol{volume}/@appconf/[appname]     # 静态配置文件\n├── home -> /vol{volume}/@apphome/[appname]    # 用户数据\n├── target -> /vol{volume}/@appcenter/[appname] # 可执行文件\n├── tmp -> /vol{volume}/@apptemp/[appname]     # 临时文件\n├── var -> /vol{volume}/@appdata/[appname]     # 运行时数据\n├── shares/                   # 共享数据目录（按 resource 配置）\n│   ├── datashare1 -> /vol{volume}/@appshare/datashare1\n│   └── datashare2 -> /vol{volume}/@appshare/datashare2\n└── wizard/                   # 向导配置（安装/卸载/升级/配置）\n    ├── install\n    ├── uninstall\n    ├── upgrade\n    └── config"},{"language":"text","snippet":"# ═══════════════ 基本信息 ═══════════════\nappname        = myapp                        # ① 应用唯一标识，系统全局唯一\nversion        = 1.0.0                        # ② 版本号：x[.y[.z]][-build]\ndisplay_name   = 我的应用                      # ③ 用户可见的名称\ndesc           = 这是一个示例应用              # ④ 详细介绍，支持 HTML 格式\nsource         = thirdparty                   # ⑤ 固定值：thirdparty\n\n# ═══════════════ 系统要求 ═══════════════\nplatform       = x86                          # ⑥ 架构：x86 | arm | all (V1.1.8+)\narch           = x86_64                       # ⑦ 已废弃，请用 platform\nos_min_version = 0.9.0                        # ⑧ 最低系统版本\nos_max_version = 0.9.100                      # ⑨ 最高系统版本\n\n# ═══════════════ 开发者信息 ═══════════════\nmaintainer     = 张三                          # 开发者/团队名称\nmaintainer_url = https://example.com           # 开发者网站\ndistributor    = 示例公司                      # 发布者\ndistributor_url = https://company.com          # 发布者网站\n\n# ═══════════════ 安装与运行控制 ═══════════════\ninstall_type   =                              # 安装位置：空=用户可选存储空间，root=系统分区\nctl_stop       = true                         # 是否显示启动/停止按钮，默认 true\ncheckport      = true                         # 是否启用端口检查，默认 true\nservice_port   = 8080                         # 应用监听端口（单个端口）\ndisable_authorization_path = false            # 是否禁用授权目录功能\n\n# ═══════════════ 用户界面 ═══════════════\ndesktop_uidir          = ui                   # UI 组件目录（相对应用根目录）\ndesktop_applaunchname  = myapp.Application    # 默认启动入口 ID\nmicro_app              = true                 # 启用微应用环境；使用前端 JS SDK (@trimjs/web-app) 调用开放 API 时必须声明\n\n# ═══════════════ 依赖管理 ═══════════════\ninstall_dep_apps = mariaDB:redis              # 依赖应用列表，格式：app1>2.2.2:app2:app3\n\n# ═══════════════ 应用更新 ═══════════════\nchangelog = 新增了XX功能                       # 更新日志（升级时展示）"},{"language":"ini","snippet":"# 格式：app1>2.2.2:app2:app3\n# > 表示最低版本要求\n# : 分隔多个依赖\n# 系统按列表顺序自动安装依赖\ninstall_dep_apps = mariaDB:redis"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: fn-fpk\ndescription: 飞牛NAS (fnOS) FPK 应用打包开发技能。使用此技能开发和打包飞牛NAS第三方应用（.fpk），包括：Native 应用（Node.js/Python/Java/Go/Shell 等）和 Docker 应用。涵盖整个开发周期：开发环境准备、fnpack 创建项目、manifest 配置、权限/资源配置、用户入口配置（应用入口 app/ui/config + 桌面图标 + 文件右键菜单）、生命周期脚本编写（cmd/main）、向导配置（wizard）、图标规范、CGI 反向代理、统一网关注册/认证、运行时环境（Python/Node.js/Java）、中间件服务（Redis/MinIO/RabbitMQ/MariaDB）、依赖管理、fnpack CLI 打包、appcenter-cli 测试安装、到上架发布；以及飞牛开放平台 Open API 接入（文件授权、页面路由、界面语言/主题、后端平台配置查询、前端 JS SDK @trimjs/web-app）。用户提到\"飞牛\"、\"fnOS\"、\"FPK\"、\"飞牛应用\"、\"开放 API\"、\"Open API\"、\"@trimjs/web-app\"、\"文件授权\"、\"授权路径\"、\"trim.file\"时触发。\n---\n\n# fn-fpk — 飞牛 NAS fnOS FPK 应用开发\n\n> 基于官方文档 https://developer.fnnas.com 于 2026-08-28 全面更新（新增开放平台 Open API 整章）。\n> 系统架构: x86_64 (AMD64) 与 ARM64 均支持；Linux 内核 6.12.18+, Debian 发行版。\n> fnpack 版本: 1.2.3, appcenter-cli 预装在 fnOS 中。\n\n---\n\n## 1. 开发环境准备\n\n### 1.1 系统要求\n- **fnOS 版本**: ≥ 0.9.27\n- **架构**: 支持 **x86_64 (AMD64)** 与 **ARM64**。manifest `platform` 字段可选 `x86` / `arm` / `all`（Docker 应用常用 `all`）；纯 Native 应用需按目标架构交叉编译。fnpack 同时提供 `linux-amd64` 与 `linux-arm64` 二进制\n- **存储**: 至少创建一个存储空间\n- **权限**: 管理员权限（安装/卸载/系统设置）\n\n### 1.2 开发方式\n- **本地开发**: 在本地用 fnpack CLI 打包，再传送到 fnOS 设备安装\n- **远程开发**: SSH 到 fnOS 设备直接开发，结合 `appcenter-cli install-local` 快速测试\n\n### 1.3 技术栈\n| 语言/框架 | 支持情况 |\n|-----------|---------|\n| Node.js | ✅ (v14/v16/v18/v20/v22) |\n| Python | ✅ (3.8/3.9/3.10/3.11/3.12) |\n| Java | ✅ (11/17/21 OpenJDK) |\n| Go | ✅ Linux 运行时支持 |\n| Shell | ✅ (Bash) |\n| HTML/JS/CSS | ✅ 前端任意框架 |\n\n### 1.4 CLI 工具\n\n#### fnpack 打包工具\n下载地址：https://static2.fnnas.com/fnpack/fnpack-1.2.3-{os}-{arch}\n\n| 平台 | 二进制 |\n|------|--------|\n| Windows x86 | `fnpack-1.2.3-windows-amd64` |\n| Linux x86 | `fnpack-1.2.3-linux-amd64` |\n| Linux ARM | `fnpack-1.2.3-linux-arm64` |\n| macOS Intel | `fnpack-1.2.3-darwin-amd64` |\n| macOS M 系列 | `fnpack-1.2.3-darwin-arm64` |\n\n安装：\n```bash\nchmod +x fnpack-1.2.3-linux-amd64\nsudo mv fnpack-1.2.3-linux-amd64 /usr/local/bin/fnpack\nfnpack --help\n```\n\n#### appcenter-cli （预装在 fnOS 中）\n```bash\n# 安装 fpk 文件\nappcenter-cli install-fpk myapp.fpk\n\n# 从本地目录安装（开发模式，无需打包）\ncd /path/to/myapp\nappcenter-cli install-local\n\n# 管理应用\nappcenter-cli list              # 已安装列表\nappcenter-cli start myapp       # 启动\nappcenter-cli stop myapp        # 停止\n\n# 手动安装功能（仅测试用途）\nappcenter-cli manual-install             # 查看状态\nappcenter-cli manual-install enable      # 开启\nappcenter-cli manual-install disable     # 关闭\n\n# 设置默认存储空间\nappcenter-cli default-volume             # 查看当前\nappcenter-cli default-volume 1           # 设置存储空间1\n\n# 静默安装（跳过向导）\nappcenter-cli install-fpk myapp.fpk --env config.env\n```\n\n---\n\n## 2. 应用类型\n\n| 类型 | 描述 | 创建命令 |\n|------|------|----------|\n| **Native 应用** | 直接运行在 fnOS 上的应用 | `fnpack create <appname>` |\n| **Docker 应用** | 基于 Docker Compose 容器编排 | `fnpack create <appname> --template docker` |\n| **纯服务应用**（无 UI） | 无 Web 访问入口 | 加 `--without-ui true` |\n\n---\n\n## 3. 项目结构与核心文件\n\n### 3.1 通用结构（fnpack create 生成）\n\n```\nmyapp/\n├── app/                      # 应用可执行文件/资源目录\n│   ├── server/               # 后台服务程序（Native 应用）\n│   ├── ui/                   # Web "},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn77pvxj7zf4hrd6q5ydgn2js5826rny\",\n  \"slug\": \"fn-fpk\",\n  \"version\": \"1.3.1\",\n  \"publishedAt\": 1787900127952\n}"},{"path":"skill-card.md","content":"## Description:\n\nGuides developers through building and packaging third-party Feiniu NAS fnOS FPK apps, including native and Docker apps, manifests, permissions, lifecycle scripts, UI entries, Open API integration, and testing or publishing workflows.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[dalingo81](https://clawhub.ai/user/dalingo81)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and engineers use this skill to produce fnOS FPK packaging guidance, shell commands, manifests, permission/resource configuration, lifecycle script examples, and Open API integration patterns for Feiniu NAS applications.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Unsafe shell examples could lead an agent to generate vulnerable NAS lifecycle scripts.\n\nMitigation: Review generated lifecycle scripts before use, validate or parse configuration files instead of sourcing them as shell, and avoid copying unsafe shell examples as-is.\n\nRisk: The skill may suggest installing fnpack system-wide from a downloaded binary.\n\nMitigation: Verify fnpack from an authenticated source before installation and confirm the selected binary matches the target OS and architecture.\n\nRisk: Generated packages could run with excessive privileges.\n\nMitigation: Keep apps running as the package user unless root is strictly required and justified for the specific application.\n\n## Reference(s):\n\n- [Feiniu fnOS Developer Documentation](https://developer.fnnas.com)\n- [Feiniu Open API Overview](https://developer.fnnas.com/api/overview/)\n- [fnpack 1.2.3 Download Pattern](https://static2.fnnas.com/fnpack/fnpack-1.2.3-{os}-{arch})\n\n## Skill Output:\n\n**Output Type(s):** [Guidance, Markdown, Code, Shell commands, Configuration]\n\n**Output Format:** [Markdown guidance with code blocks, configuration snippets, and shell commands]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [May include fnpack and appcenter-cli command examples plus fnOS manifest, resource, privilege, UI, wizard, and lifecycle-script snippets.]\n\n## Skill Version(s):\n\n1.3.1 (source: server release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment."}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"飞牛NAS (fnOS) FPK 应用打包开发技能。使用此技能开发和打包飞牛NAS第三方应用（.fpk），包括：Native 应用（Node.js/Python/Java/Go/Shell 等）和 Docker 应用。涵盖整个开发周期：开发环境准备、fnpack 创建项目、manifest 配置、权限/资源配置、用户入口配置（应用入口 app/ui/config + 桌面图标 + 文件右键菜单）、生命周期脚本编写（cmd/main）、向导配置（wizard）、图标规范、CGI 反向代理、统一网关注册/认证、运行时环境（Python/Node.js/Java）、中间件服务（Redis/MinIO/RabbitMQ/MariaDB）、依赖管理、fnpack CLI 打包、appcenter-cli 测试安装、到上架发布；以及飞牛开放平台 Open API 接入（文件授权、页面路由、界面语言/主题、后端平台配置查询、前端 JS SDK @trimjs/web-app）。用户提到\"飞牛\"、\"fnOS\"、\"FPK\"、\"飞牛应用\"、\"开放 API\"、\"Open API\"、\"@trimjs/web-app\"、\"文件授权\"、\"授权路径\"、\"trim.file\"时触发。 Skill: fn-fpk Owner: dalingo81 Summary: 飞牛NAS (fnOS) FPK 应用打包开发技能。使用此技能开发和打包飞牛NAS第三方应用（.fpk），包括：Native 应用（Node.js/Python/Java/Go/Shell 等）和 Docker 应用。涵盖整个开发周期：开发环境准备、fnpack 创建项目、manifest 配置、权限/资源配置、用户入口配置（应用入口 app/ui/config + 桌面图标 + 文件右键菜单）、生命周期脚本编写（cmd/main）、向导配置（wizard）、图标规范、CGI 反向代理、统一网关注册/认证、运行时环境（Python/Node.js/Java）、中间件服务（Redis/MinIO/RabbitMQ/MariaDB）、依赖管理、fnpack CLI 打包、appcenter-cli 测试安装、到上架发布；以及飞牛开放平台 Open API","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":1068,"uniquenessScore":51,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-11T01:40:48.672Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-11T01:40:48.672Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-11T03:55:51.691Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}