> 本文为 SPKG 1.0 第三方原生程序合同。已实现的官方平台模块采用 [SPKG 1.1 扩展](OFFICIAL-MODULES.md)，共享相同签名封装并通过独立模块引擎安装。

# ApuOS SPKG 套件规范 v1.0

状态：**开发格式已落地，运行时安装引擎尚未接入**。本文与 JSON Schema、Rust `spkg` 工具共同定义格式 1.0；不表示 NAS 已支持执行第三方套件。产品版本使用 X.YY.ZZZ；套件自身使用无前导零的 `major.minor.patch`，例如 1.2.3，两种版本独立。

## 1. 文件封装

后缀 `.spkg`，整个文件按字节排列：

```
[zstd压缩的USTAR][UTF-8 JSON签名区][签名区长度:u32 little endian][SPKGv1\r\n:8字节]
```

最后 12 字节固定。先解析尾部长度与 magic，分离压缩数据与签名区；不得把含签名尾部的完整文件直接交给通用解压工具。

所有JSON层级拒绝重复键；签名区恰好五个字段，不接受未知字段：

```json
{"format_version":"1.0","payload_size":12345,"sha256":"<64位小写十六进制>","key_id":"<公钥SHA256>","signature":"<Ed25519签名，128位十六进制>"}
```

- `payload_size` 是压缩 USTAR 的字节数；`sha256` 是此段 SHA-256。索引中的 `sha256` 则是**整个 .spkg 文件**的摘要，两者不能混淆。
- `key_id` 是外部受信任 Ed25519 公钥 32 字节的 SHA-256。包内不携带自动受信任的公钥。
- 签名消息：ASCII `APUOS-SPKG-V1` + 单个 NUL + 压缩数据摘要的 32 原始字节 + 压缩长度的 8 字节 little endian + key_id 的 64 ASCII 字节。Ed25519 strict 校验，绑定内容、长度和签名者。
- 未签名开发包的 `key_id` 与 `signature` 必须同时为 JSON null；仅可通过工具显式 `--allow-unsigned` 进行结构检查。生产安装不得自动接受未签名包，也不得将“结构通过”解释为可信。
- `manifest.signature` 不存在，避免签名自身引起循环；旧设计的空 signature 字段需移除。
- 不依赖 JSON 键顺序对包签名；签名直接覆盖压缩段。包/索引发布后必须不可变。

限制：压缩段 ≤64 MiB，解压 USTAR ≤128 MiB，签名区 ≤16 KiB，最多 10,000 个条目；manifest ≤64 KiB，单个生命周期脚本 ≤1 MiB。大应用后续扩展新格式/限额，不默默绕过限制。

## 2. 归档目录

```
manifest.json
bin/arm64/<程序>
bin/x86_64/<程序>
conf/<默认配置模板>
assets/<图标及截图>
lib/<私有库>
scripts/<可选生命周期脚本>
```

- manifest 位于根目录，不加套件名外层目录。顶层只有上述六项。
- USTAR 常规文件或目录；禁止符号链接、硬链接、设备、FIFO、稀疏文件、GNU/PAX 扩展。
- 路径为 ASCII 字母数字及 `/._-`，≤100 字节，不接受绝对路径、空段、`.`、`..`、反斜杠、重复成员或文件/目录冲突。
- 文件 mode 为 0644 或 0755；目录为 0755 且长度为零。归档 uid/gid 为 0，仅表示可复现的元数据，**不表示以 root 安装或执行**。引擎必须按套件账户重新赋予所有权。
- 打包器按路径排序，uid/gid/mtime 归零；bin 与 scripts 文件统一 0755，其余为 0644。不把本机账户、时间戳和隐藏构建文件打入包。
- 不允许在 TAR 结束标记后隐藏额外内容。先完整验证，在新建临时目录安全解包，绝不能直接解压到 `/` 或信任现有软链接。

## 3. Manifest

以 [manifest.schema.json](manifest.schema.json) 为机器校验定义；未知字段或重复语义不得偷偷启用权限。样例见 [hello/manifest.json](../../examples/packages/hello/manifest.json)。

| 字段 | 定义 |
| --- | --- |
| format_version | 固定字符串 `1.0`，独立于产品/套件版本 |
| pkg_id | 小写反向域名，至少三段，≤128字符；例如 com.example.hello |
| name / author | 非空显示名称/作者，各≤100字符；作者字符串不代表认证 |
| version | 三段数字，无前导零；1.0 首版不支持预发布或 build 后缀 |
| architecture | 唯一的 x86_64、arm64，支持一或两个架构；aarch64/amd64 在主机探测时规范化 |
| category | system/storage/network/media/development/security/business，显示端翻译类别 |
| description / release_notes / license | 描述、更新记录、SPDX许可证标识或明确的许可证名称 |
| icon / screenshots | 包内 assets 路径，图片须实际存在，最多8张；后续发布端清洗SVG或栅格化，禁止直接插入不可信SVG/HTML |
| dependencies.system_packages | 发行版包名，可选 ` >= 版本`；仅约束，不是可执行命令 |
| dependencies.spkg_packages | pkg_id + ` >= 无前导零三段版本`，禁止自依赖；从原草案的 spk_packages 统一为 spkg_packages |
| permissions | network:bind/network:outbound/filesystem:read/filesystem:read_write，去重 |
| default_port | 1024–65535；与 network:bind 必须同时声明，首版一个端口 |
| runtime.entrypoint | `bin/{architecture}/程序`；每个声明架构都必须有0755常规文件 |
| runtime.arguments | 参数数组，逐个传入 exec，不由shell拼接；最多32项，每项≤1024字符 |
| runtime.memory_mb | 16–65536 MiB的声明上限，引擎依主机可用量再限制 |
| runtime.cpu_quota_percent | 1–100，按单核份额解释，未来执行器负责转换systemd/cgroup配置 |
| runtime.minimum_apuos | 可选，X.YY.ZZZ最低产品版本；按数值比较，不用字符串排序 |
| runtime.supported_os | 可选且非空，例如 debian:13、ubuntu:24.04；发布/安装验收仍须检查glibc/动态库 |

Schema 校验并不保证 ELF 架构或 ABI、图标内容、安全性、依赖存在或资源足够。Rust 工具额外核对引用文件、权限与端口、路径、自依赖；运行引擎负责实际兼容性、端口占用、依赖闭环、资源及Linux身份检查。示例脚本刻意不是产品二进制。

## 4. 权限与执行边界（后续引擎的强制合同）

- 用户 API 延续读取 `native.read`、管理 `native.manage` 的能力校验；安装、卸载、升级必须计划预览与明确确认。运行账户与登录账户分开。
- 每个套件独立 Linux UID/GID；network:bind 只允许已声明非特权端口，network:outbound 表示有意向访问外部网络。
- filesystem 权限只覆盖本套件配置/数据；访问用户共享目录必须另走文件身份代理/ACL授权。声明 read_write 不授予全盘或其他套件权限。
- 不接受 root、shell、disk:format、driver:install、host:write 等任意高权限声明；必须拒绝未知权限。
- 控制 API 不执行包内 shell；独立受限执行器管理 lifecycle，shell脚本使用套件账户、固定解释器、受限环境、工作目录、超时/cgroup。不能继承 NAS 服务密钥、token 或安装sudo权限。
- systemd服务创建由专门的白名单 broker 模板完成；脚本不能直接运行 sudo/systemctl、修改用户或软件源。包管理也只能复用已有受限助手和变更计划。
- cgroup、UID隔离不等于安全沙箱；网络监听约束不能仅靠声明保证。安装第三方脚本前必须完成实际策略、测试与审计。

## 5. 生命周期合同

仅支持固定脚本名：`pre-install.sh`、`install.sh`、`post-install.sh`、`pre-uninstall.sh`、`uninstall.sh`、`start.sh`、`stop.sh`、`upgrade.sh`。全部可选。工具只检查名称/大小/权限，**不执行脚本，也不宣称进行语义安全扫描**。

环境由执行器设置：`APUOS_PKG_ID`、`APUOS_PKG_VERSION`、`APUOS_PKG_PREVIOUS_VERSION`（无旧版为空）、`APUOS_PKG_ROOT`、`APUOS_PKG_CONFIG`、`APUOS_PKG_DATA`、`APUOS_PKG_PORT`（无监听为空）。脚本 `#!/bin/sh` + `set -eu`；0成功，其他失败；禁止把命令/凭据从manifest拼接到shell。

默认每阶段120秒，有限度记录 stdout/stderr，凭据脱敏；超时终止整个进程组。安装/升级按 pkg_id 串行，系统依赖操作全局串行。依赖解析须检测循环、版本冲突、计划过期，并在执行前重新校验包哈希、信任与设备状态。

后续存储布局：不可变代码 `/opt/apuos/packages/<pkg_id>/versions/<version>/`，配置 `/etc/apuos/packages/<pkg_id>/`，数据 `/var/lib/apuos/packages/<pkg_id>/`；引擎原子切换当前版本指针，用户不能借包路径跳出根目录。

流程：下载/本地上传隔离 → 校验 → 预览与确认 → 依赖准备 → 配置备份/代码暂存 → pre-install/install/post-install → 服务健康检查 → 原子发布。卸载先停止服务，执行固定卸载步骤；业务数据默认保留，清除另需确认。升级必须声明/验证数据迁移策略；不能声称回滚系统依赖、已写入数据库或外部网络副作用。失败保留日志与恢复点，不重复执行破坏性阶段；重启后对未完成任务进行恢复审计。

## 6. 仓库与来源合同

源根 `/index.json` 符合 [index.schema.json](index.schema.json)，完整索引≤4 MiB；相同 `(pkg_id, version, architecture集合)` 只可出现一次。package URL 为同源 `packages/...spkg` 相对路径，禁止遍历、查询参数、跨源重定向。JSON Schema 之外需校验URL规范化、字段与包manifest完全一致、key_id与信任表一致、整个文件size/sha256一致。

- 默认 HTTPS +证书验证；局域网HTTP只作后续管理员明确授权选项，仍要求签名，不能自动降级。
- `/index.sig` 为JSON：`format_version: "1.0"`、`key_id`、`signature`。签名消息为ASCII `APUOS-SPKG-INDEX-V1` + NUL + **index.json原始字节的32字节SHA256**；不能重新序列化后验证。
- 官方源也需有效信任密钥；第三方源先由管理员导入并核对密钥指纹。认证开发者、审核状态与官方身份来自可信后台，不能由 author 字段自报。
- `revision` 按源单调递增，持久化最高值防回放；HTTP ETag/304可缓存，首版全量索引，增量协议以后版本另定。
- 私有源token只在后端密钥库保存，不写索引/日志/浏览器URL。来源主机和重定向都必须经过SSRF及允许网段策略校验。
- 撤回保留已发布记录，标记 withdrawn；不更换同版内容。密钥撤销与轮换需要独立的受信任管理机制，不能依靠不可信索引自行授予信任。

## 7. 兼容与演进

格式1.0、Schema和工具锁定本合同；新增不兼容字段、权限、签名算法或封装升级 format_version。本文是上传后台的接入标准，但不会为现有APT应用虚构SPKG安装记录。完整后端上线前需在隔离 ARM64/X86 Linux 主机验收安装、升级、权限拒绝、断电恢复和数据保留，不能在只有系统eMMC的NAS上做破坏性测试。
