包规范
本节定义 TOS 7 应用包的正式规范。所有应用必须符合本规范。
应用生命周期
TOS 7 应用遵循明确定义的生命周期:
安装 ──► 配置 ──► 启动 ──► 运行中
│ │ │ │
│ │ │ ├── 停止 ──► 已停止 ──► 启动(重启)
│ │ │
│ │ └── 崩溃 ──► 自动重启(如已配置)
│ │
│ └── 升级 ──► 停止 ──► 安装新版 ──► 迁移 ──► 启动
│
└── 卸载 ──► 停止 ──► 清理 ──► 移除
Deb 应用的生命周期阶段:
| 阶段 | 触发条件 | 脚本/操作 | 预期行为 |
|---|---|---|---|
| 安装前 | dpkg -i | DEBIAN/preinst | 创建用户、检查前置条件、创建目录 |
| 安装 | dpkg -i | 包解压 | 文件按打包规范部署(参见第 8 章);应用看到的是 /usr/local/<appid>/,平台将其解析到用户所选数据卷上的 /Volume<N>/@apps/<appid>/ |
| 安装后 | dpkg -i | DEBIAN/postinst | 设置权限、启用服务、启动服务 |
| 启动 | systemctl start | systemd / init.d | 应用进程启动 |
| 停止 | systemctl stop | systemd / init.d | 应用进程优雅停止 |
| 卸载前 | dpkg --remove | DEBIAN/prerm | 停止服务 |
| 卸载后 | dpkg --remove | DEBIAN/postrm | 清理用户、数据、残留文件 |
| 升级 | dpkg -i(新版本) | prerm → 升级 → postinst | 停止旧版、安装新版、迁移数据、启动 |
Docker 应用的生命周期阶段:
| 阶段 | 触发条件 | 操作 | 预期行为 | 补充说明 |
|---|---|---|---|---|
| 安装 | 应用中心(用户点击「安装」按钮) | 拉取镜像、创建卷 | 镜像可用、数据目录已创建 | 平台自动执行安装流程,开发者无需额外干预 |
| 启动 | 应用中心(用户点击「启动」按钮)/ docker-compose up | 启动容器 | 服务可访问 | 支持用户手动在命令行启动,与平台操作逻辑一致 |
| 停止 | 应用中心(用户点击「停止」按钮)/ docker-compose down | 停止容器 | 服务已停止,数据保留 | 仅停止容器进程,不会删除挂载的数据卷 |
| 升级 | 应用中心(用户点击「更新」按钮,存在新版本) | 拉取新镜像、重建容器 | 零停机或短暂停机 | 建议应用支持平滑升级,避免数据中断 |
| 卸载 | 应用中心(用户点击「卸载」按钮) | 移除容器、可选清理卷 | 所有资源释放 | 用户可选择是否保留数据卷,避免误删数据 |
说明:"应用中心"指TOS系统内置的应用管理界面,用户通过该界面执行的安装/启动/停止/升级/卸载操作,均会触发对应生命周期流程。当前 TOS 版本中,Docker 应用不支持平台升级 —— 如需将 Docker 应用升级到新版本,用户必须先卸载再重新安装(参见第 9 章 · Docker开发)。
版本号规范
平台行为(当前): 开发者平台不校验版本号。非标准格式、重复版本号、不递增的版本号均不会被驳回。平台只读取 config.ini 中的 version 字段并在应用中心展示。因此以下规则是为兼容性给出的建议,而非提交要求。
为什么格式仍然重要 —— 更新判定:
TOS 应用中心目前通过逐段按数值比较版本号来判断是否有更新。这是功能依赖,而非提交规则:
- 新版本数值上大于已安装版本时,用户会看到更新提示。
- 若不大于(相等、更低,或无法按数值比较,如
v2、1.0.0-rc1),该更新可能无法被识别 —— 包仍会被接收,但可能永远推不到存量用户。
推荐格式:
| 规则 | 说明 |
|---|---|
| 字符 | 推荐仅使用数字(0-9)和点(.)。字母和连字符(如 v1.2、1.2.3-beta)无法按数值比较,可能导致更新判定失效 |
| 段数 | 1 到 3 个数值段(如 1、1.2、1.2.3) |
| 单段长度 | 不限制单段长度,每段可包含任意位数 |
| 总长度 | 不强制限制长度。仍建议保持版本号简短(此前的限制为 20 个字符) |
| Beta 版本 | 使用 config.ini 中的 "beta": true 字段标记测试版。-beta / -rc / -alpha 等后缀不会被驳回,但会破坏数值比较 |
版本排序(比较规则):
版本号逐段按数值从左到右比较:
| 规则 | 说明 |
|---|---|
| 数值比较 | 每一段按整数比较(忽略前导零,如 01.2 等于 1.2) |
| 缺失段 | 缺失的段按 0 处理(如 1.2 等于 1.2.0;1 等于 1.0.0) |
| 比较结果 | 第一处数值不同的段决定先后顺序 |
各文件间的版本一致性:
平台不会将一个包内部的多个版本号相互比较,也不要求它们一致。版本从 config.ini 读取。
| 位置 | 字段 | 读取方 |
|---|---|---|
config.ini | version | 开发者平台与 TOS 应用中心 —— 这是展示给用户的版本 |
DEBIAN/control(仅 Deb 应用) | Version | Debian 包管理器(dpkg)—— 这是系统记录的版本 |
- 仍建议保持一致。 不一致不会被驳回,但会造成混乱的安装结果 —— 用户在应用中心看到的版本与
dpkg -l报告的版本不同,这会给升级和技术支持带来困扰。 - 保持
DEBIAN/control的Version不递减。 在dpkg层面,安装Version低于已安装版本的包会被拒绝(降级保护)。这是包管理器行为,与开发者平台无关。 - 版本来源: 版本不在开发者平台手动填写,GitHub/Gitee 的 Release 标签也不用于确定版本 —— 版本始终来自你所选包内
config.ini的version字段。 - 回滚: 平台不会自动将应用回滚到更小的版本号。如需回滚,请在开发者平台提交回滚申请(参见第 17 章)。
Beta 版本管理:
Beta 状态由 config.ini 中的 beta 标志控制,而非版本号字符串:
| 发布类型 | config.ini 填写 | 平台展示 |
|---|---|---|
| 第一个测试版 | "version": "1.0.0", "beta": true | 1.0.0(Beta) |
| 第二个测试版 | "version": "1.0.1", "beta": true | 1.0.1(Beta) |
| 正式版 | "version": "1.0.2", "beta": false | 1.0.2 |
-beta、-rc、-alpha等版本号后缀不会被驳回,但会破坏数值比较。请改用"beta": true字段,以便 Beta 用户仍能收到更新。- 多个测试版通过递增数值部分区分(建议递增修订号)。
- 转正式版时设置
"beta": false。版本号可以保持不变;若一并递增,已安装该 Beta 的用户也会收到更新。 - 版本号低于此前已提交 Beta 构建的正式版,不会作为更新推送给仍停留在该 Beta 的用户。
- 详见附录 M Beta 版应用管理
Release 资源命名规范
将应用包上传至 GitHub/Gitee Releases 时,请使用以下推荐名称。平台实际依赖的是文件扩展名:.deb 视为单包 Deb,.tar.gz 视为双包 Deb 归档或 Docker 包(与创建应用时声明的应用类型一致)。
平台不校验包文件名,也不再拿文件名去比对 <app_id> 或 <platform>。版本从 config.ini 的 version 字段读取,无需出现在文件名中,文件名也不再与任何版本做匹配。包的身份在下载解析之后校验:以 config.ini(id、platform、包类型)与你在开发者平台填写的信息比对。
| 应用类型 | 包格式 | 推荐名称 | 示例 |
|---|---|---|---|
| Deb(单包模式) | .deb 文件 | <app_id>_<platform>.deb | myapp_x86_64.deb |
| Deb(双包模式) | .tar.gz 压缩包 | <app_id>_<platform>.tar.gz | myapp_x86_64.tar.gz |
| Docker 应用 | .tar.gz 压缩包 | <app_id>.tar.gz | myapp.tar.gz |
字段定义(推荐,非强制):
<app_id>:建议与config.ini中的id字段一致(区分大小写)<platform>:建议与config.ini中的platform字段一致,且只能是两个受支持取值之一(x86_64或aarch64)。不接受多值或all。多架构支持需按目标架构分别构建并提交。平台不从文件名读取架构 —— 它在解析包之后校验架构。
Release 标签: GitHub/Gitee 的 Release 标签不用于确定版本,也不必与 config.ini.version 一致。任意标签均可,仅建议按版本号命名(如 v1.0.0 或 1.0.0)以便阅读。
升级
Deb 应用升级:
- 升级时
preinst收到$1 = "upgrade"参数 postinst收到$1 = "configure"参数,$2为旧版本号- 使用
$2检测旧版本并执行数据迁移 - 升级过程中绝不删除用户数据,仅修改配置格式或迁移数据结构
- 用户将持久化业务数据存储在
/Volume<N>/<appid>/共享文件夹中,该文件夹由应用通过ter_share_add创建。平台在应用升级或重装时不会删除或覆盖此共享文件夹中的用户数据 - 运行时数据(缓存、临时文件)存储在
/usr/local/<appid>/data/中,可安全重新生成 - 建议不要将数据存储在
/etc、/var、/usr/bin等系统公共目录,此类目录可能因系统更新或应用升级被覆盖,导致数据丢失
# 示例:postinst 中包含迁移逻辑
case "$1" in
configure)
if [ -n "$2" ]; then
# 从版本 $2 升级
if dpkg --compare-versions "$2" lt "2.0.0"; then
# 将 v1.x 配置格式迁移到 v2.x
/usr/local/<appid>/bin/migrate.sh "$2"
fi
else
# 全新安装
echo "全新安装"
fi
;;
esac
Docker 应用升级:
- 拉取新的镜像标签
- 使用现有卷挂载重建容器
- 通过持久化卷在升级间保留数据(平台会将其解析到应用数据根目录
/Volume<N>/DockerAppData/<appid>/) - 如有需要,在应用入口脚本中包含迁移逻辑
- Docker 应用不使用 TNAS 共享文件夹:平台不会为其创建共享文件夹,所有持久化业务数据都保存在容器的卷挂载中
兼容性矩阵
| TOS 版本 | 基础系统 | glibc | Python3 | Docker | Node.js |
|---|---|---|---|---|---|
| TOS 7.0 | Ubuntu 22.04 兼容 | 2.35 | 3.10 | 20.10+ | 18.x |
| TOS 7.x | Ubuntu 22.04 兼容 | 2.35 | 3.10 | 20.10+(或更高) | 18.x(或更高) |
Node.js 版本仅供 Docker 容器内使用参考,Deb 应用不可直接依赖。
应用必须通过 config.ini 中的 low_version 声明最低 TOS 版本要求。平台将自动过滤不兼容的设备。
TOS 7.x 小版本兼容性: TOS 7.x 系列小版本(含 7.1 及以上)将保持核心依赖(glibc/Python3/Docker/Node.js)的 ABI/API 兼容性,兼容于 Ubuntu 22.04 的根文件系统。为 TOS 7.0 开发的应用无需额外适配即可运行。
TOS 7 小版本兼容性:
low_version字段必须指定所需的最低 TOS 版本- 提交更新时,请在最新的 TOS 7 小版本上测试
大小写敏感规范
TOS 采用兼容 Ubuntu Linux 的根文件系统,文件系统严格区分大小写。所有应用必须遵循以下规则:
| 要素 | 规则 |
|---|---|
| 文件名 | 严格匹配大小写。config.ini ≠ Config.ini ≠ CONFIG.INI |
| 目录名 | 严格匹配大小写。/images/icons/ ≠ /Images/Icons/ |
| config.ini 键名 | 所有键名小写。"version" 正确,"Version" 错误 |
应用 ID(id) | 严格匹配大小写。MyApp ≠ myapp 。创建后不可修改 |
| Systemd 服务名 | 必须严格匹配,区分大小写 |
在单个应用包中使用同一文件或目录的大小写变体。这会导致 Linux 上出现"找不到文件"和"服务启动失败"错误。
跨平台换行符规范(CRLF to LF)
所有在 TOS 系统(Linux 环境)中运行的脚本和配置文件,必须使用 LF(\n)作为换行符,禁止使用 Windows 默认的 CRLF(\r\n)换行符。
问题影响
- 脚本执行报错
bad interpreter: No such file or directory - 配置文件解析失败(如 systemd 服务文件、Nginx 配置)
- 解释器路径被错误识别为
/bin/bash\r等不存在的二进制
强制要求
- 所有
.sh/.py/.ini/.lang/.service/.conf文件,提交前必须转为 LF 换行 - Deb 包构建脚本中,必须加入自动转换逻辑,避免构建过程中引入 CRLF
推荐修复方案
方案 1:在构建脚本中自动转换(推荐)
import os
def convert_crlf_to_lf(file_path):
with open(file_path, "rb") as f:
content = f.read()
content = content.replace(b"\r\n", b"\n")
with open(file_path, "wb") as f:
f.write(content)
# 打包前,遍历所有需要转换的文件
for root, _, files in os.walk("your_app_source/"):
for name in files:
if name.endswith((".sh", ".py", ".ini", ".lang", ".service", ".conf")):
convert_crlf_to_lf(os.path.join(root, name))
方案 2:本地开发工具配置
- VS Code:右下角状态栏点击
CRLF,切换为LF后保存 - Git 全局配置(避免后续文件自动转为 CRLF):
git config --global core.autocrlf input