- 后端
- 容器运行时
- 安全
- 云原生
【免费下载链接】sandstorm
Sandstorm is a self-hostable web productivity suite. It's implemented as a security-hardened web app package manager. | Actively sponsored by our friends at TestMu AI
Sandstorm 应用本质上是一个包含完整用户态(全部二进制、库、模块)的加密签名压缩包(.spk),而spk工具链允许你跳过vagrant-spk等辅助框架,直接手工构建这种包。本指南以官方原始打包教程为主体,结合本仓库的spk实现源码与真实包定义示例,带你从零走完"编写应用 →spk init生成包定义 →spk dev采集依赖 →spk pack产出可分发包"的完整流程,并深入理解包定义文件的每个字段、签名与密钥环机制,使你能独立打包任何能在 Linux 上运行的技术栈应用。
什么是"原始打包"(Raw Packaging)
Sandstorm 的打包工具有两代思路:vagrant-spk通过虚拟机辅助构建,适合快速上手;而本文介绍的raw packaging直接使用spk命令行工具,让你完全掌控包的生成过程,从而更深入地理解 Sandstorm 的包格式与运行模型。本仓库的 spk.c++ 就是该工具的实现,其命令行帮助信息将其定位为:
"Tool for building and checking Sandstorm package files."(构建与校验 Sandstorm 包文件的工具)
一个 Sandstorm 应用包包含运行应用所需的整个用户态——所有二进制、库、模块等。正常情况下,逐一手工确定该把哪些文件放进包是极其繁琐的。Sandstorm 用一个巧妙的技巧解决这个问题:它在你开发机器上观察正在运行的服务器,把服务器实际用到的所有文件自动拉入包中。这个"观察"机制在源码层面由 FUSE 文件系统实现(见下文"开发模式"一节)。
提示:如果你是打包新手,或主力操作系统不是 Linux,建议先阅读 五分钟打包教程,再回到本文深入学习。
环境准备:前置条件
在开始之前,你需要满足以下条件:
了解 Sandstorm 的基本概念
- 先实际试用 Sandstorm(官方演示站)以感受其运行方式;
- 阅读 App 开发者手册 理解 Sandstorm 应用面临的更高层设计问题。
在本地机器安装 Sandstorm 开发环境
- 安装 Linux,内核版本需为 3.10 或以上(Ubuntu 14.04 及以上即可满足);
- 安装 Sandstorm 官方服务器:
curl https://install.sandstorm.io | bash; - 该本地服务器将用于开发联调,请确保其处于运行状态;
- 务必把自己加入服务器用户组(通常名为
sandstorm)。修改用户组后可能需要注销并重新登录才能生效——spk dev依赖这个组成员身份与本地服务器通信(spk.c++ 中dev命令的说明明确要求"Your user account must be a member of the server's group, typically 'sandstorm'")。
安装完成后,spk可执行文件位于 Sandstorm 安装目录(如/opt/sandstorm/latest/bin/spk),默认的包定义 schema 文件位于/opt/sandstorm/latest/usr/include/sandstorm/package.capnp,仓库内的 package.capnp 就是它的同源副本,注释中包含对包定义格式的完整说明。
框架特化工具一览
对某些框架,Sandstorm 社区提供了特化工具或指南,可以更轻松地打包:
- Meteor:使用
meteor-spk工具(本仓库的 meteor-testapp/sandstorm-pkgdef.capnp 就是一个基于 Meteor 的真实包定义示例,其myCommand通过sandstorm-http-bridge启动node start.js); - Python:参见 Python 打包指南;
- Ruby on Rails:参见 Ruby on Rails 打包指南;
- 纯客户端 / 浏览器端应用(Unhosted / remoteStorage 风格):参见 纯客户端应用移植指南。
即使你的框架在上面的列表里,也仍然应该完整阅读本页,以便更深入地理解 Sandstorm 的运行机制。
打包流程:从编写应用到产出 .spk
下面用一个最简单的 Node.js 应用走完五个通用步骤。
第一步:编写应用
你可以用任何能在 Linux 上运行的技术栈编写应用,按平时的方式写 Web 应用即可。唯一的硬性约束是:应用的所有数据必须存放在/var下,因为在 Sandstorm 中运行时,文件系统的其余部分都是只读的。
为了本教程,我们使用下面这个极简 Node.js 应用(文件名为main.js):
var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }).listen(10000, '127.0.0.1'); console.log('Server running at http://127.0.0.1:10000/');确保本机已安装 Node,使你在应用源码目录执行node main.js时它能够正常运行。
第二步:创建包定义:spk init
在应用的源码目录中执行:
spk init -p 10000 -- node main.js该命令告诉 Sandstorm:启动应用的命令是node main.js,应用启动后监听 10000 端口的 HTTP 接口。命令执行后会生成sandstorm-pkgdef.capnp文件,其中保存了新的配置——你可以打开查看,大量字段都可以自行调整。
spk init的完整选项(来自 spk.c++ 中getInitMain()的实现,L535-L563):
| 选项 | 含义 |
|---|---|
-p <port> | 设置应用监听的 HTTP 端口(即<command>将绑定的端口),应用将使用 Sandstorm 的 HTTP 桥(sandstorm-http-bridge)而不是裸 Sandstorm API。端口必须在 1024~65535 之间,低于 1024 的特权端口在沙箱内不可用;同时-p与-r互斥 |
-r/--raw | 声明应用直接实现裸 Sandstorm API(Cap'n Proto 协议),不经过 HTTP 桥;与-p互斥。-p与-r至少必须指定其一 |
-A/--include-all | 直接包含-I指定目录下的全部内容,而不是在 dev 模式下动态探测所需文件(使用-A时必须至少指定一个-I) |
-I <path> | 向依赖文件的搜索路径中追加一个目录,可多次指定;不指定时默认搜索.(当前目录)再搜索/(系统根目录,且默认隐藏敏感目录) |
-o <filename> | 把包定义写到指定文件而非sandstorm-pkgdef.capnp(-o -表示写到标准输出) |
-i <app-id> | 使用给定的应用 ID 而非新生成一个(ID 只能包含字母数字) |
spk init生成的包定义默认采用"开发模式动态收集依赖"方案(即写入fileList = "sandstorm-files.list")。如果你在 dev 模式下没有运行过应用就执行spk pack,工具会提示"sandstorm-files.list" does not exist. Have you run "spk dev" yet?(见 spk.c++ L1047-L1052)。
第三步:开发模式测试:spk dev
仍然在包目录下执行:
spk devspk dev会临时把你正在开发的应用注册到本地 Sandstorm 服务器上。此时浏览服务器,打开文件菜单,就能看到该应用并创建新实例——请创建实例并确认其工作正常。
开发模式下的关键注意事项:
- 务必测试应用的所有功能。Sandstorm 正在观察应用运行时打开的所有文件,并据此生成运行时依赖清单。如果某个功能在 dev 模式下没有测试到,那么它用到的文件可能不会被收录进包,生产环境就会出问题;
- 应用的控制台(debug)输出可以通过点击顶栏的控制台图标查看;
- 如果应用日志不足以定位问题,可以查看 Sandstorm 服务器的主日志:
/opt/sandstorm/var/log/sandstorm.log- 测试结束后,在终端按
Ctrl+C退出 dev 模式。
从源码层面看,spk dev的运行机制非常精巧(spk.c++ L1890-L2156):
- 它首先定位本地 Sandstorm 服务器(默认根据
spk可执行文件的安装位置推断,或通过/etc/init.d/sandstorm定位,找不到时可用-s <dir>显式指定); - 通过 Unix socketpair 向服务器的
sandstorm dev子命令传递应用 ID,并接收服务器返回的FUSE 文件描述符; spk用makeUnionFs()将sourceMap映射的目录(含sourceMap指定的所有源路径与运行时目录)合并挂载成一个 FUSE 文件系统(L2056);- 应用进程对文件的每次打开(open)都会被 FUSE 层拦截,路径被记录进
usedFiles集合(L2053-L2055); - 退出时,
spk dev把usedFiles与既有sandstorm-files.list合并后重写该文件(L2123-L2156)。新文件会被加入,但旧文件不会被自动移除;想重置清单,直接删除sandstorm-files.list再跑一次spk dev即可(这也是 package.capnp 中fileList字段注释明确说明的行为)。
spk dev的常用选项:
| 选项 | 含义 |
|---|---|
-s <dir> | 连接到安装在<dir>的 Sandstorm 服务器 |
-m <dir> | 不连接服务器,仅把包内容挂载到<dir>便于手动探查 |
-c/--cache | 开启 FUSE 文件系统激进缓存以提升性能,但意味着每次修改代码后都必须重启spk dev才能生效 |
--proc | 在沙箱内挂载/proc,便于调试(仅开发模式可用,打包后的应用不会获得/proc访问权) |
第四步:检查文件清单
spk已经生成了一个名为sandstorm-files.list的文本文件,列出应用用到的所有文件。用文本编辑器打开并确认内容合理,重点检查:
- 是否混入了你系统中的个人文件。默认配置会隐藏
/home和/var,能阻止大多数泄漏;具体而言,spk init生成的默认sourceMap在映射系统根目录/时会隐藏home、proc、sys、etc/passwd、etc/hosts、etc/host.conf、etc/nsswitch.conf、etc/resolv.conf(spk.c++ L653-L662),而/dev、/var、/tmp由 Sandstorm 自身提供,因此隐式隐藏; - 特别留意来自
/etc的文件。很多应用依赖/etc中的配置才能基本运行,但/etc里的文件通常与你的主机系统高度绑定,未必适合放进应用包。若应用需要覆盖某个/etc文件,只需在源码树中创建etc目录并放入不同版本的文件——默认的sandstorm-pkgdef.capnp把.映射到包根目录/,因此./etc/foo会优先于/etc/foo满足包对etc/foo的需求。
如果发现清单中混入了不应包含的文件,可以:
- 从清单中手动删除该行;
- 编辑
sandstorm-pkgdef.capnp,把这些文件列入hidePaths,这样下次运行 dev 模式时不会被重新加入; - 建议重新运行
spk dev并再次测试应用。
提示:
sandstorm-files.list是自动生成文件,每次 dev 模式运行结束都会被按排序重写(文件头有*** WARNING: GENERATED FILE ***警告,spk.c++ L2148-L2155)。你可以手动增删条目,但不要依赖其中的注释与顺序。
第五步:构建分发包:spk pack
执行:
spk pack my-app.spk这会构建出可分发、可安装的my-app.spk。你可以通过任意 Sandstorm 服务器的/install路径上传安装它。
spk pack的实现(spk.c++ L959-L1073)大致如下:
- 读取
sandstorm-files.list(若使用-A收集模式则跳过),并通过sourceMap把每个条目映射到真实源文件,同时合并alwaysInclude中列出的文件/目录(目录会递归包含全部内容); - 自动向包中注入特殊文件:空的
dev、tmp、var目录与空的proc/cpuinfo(这些会在运行时被 supervisor 覆盖挂载)、序列化后的sandstorm-manifest、以及使用 HTTP 桥时所需的sandstorm-http-bridge与sandstorm-http-bridge-config(L1039-L1043、L1236-L1259); - 对未压缩的归档做 SHA-512 哈希,用应用私钥(Ed25519/libsoodium
crypto_sign)对哈希签名,把"魔数 + XZ 压缩后的(签名 + 归档)"写入最终文件; - 包的解压大小上限为1 GiB,超限会直接拒绝打包(L984-L989,该限制是为了宿主机的安全)。
验证与解包:spk verify与spk unpack
发布前建议用spk verify my-app.spk校验包签名并查看从 manifest 提取的详细信息(默认输出 JSON 格式,含 appId、packageId、标题、版本、作者 PGP 指纹、元数据等,见 spk.c++ L1814-L1880 与 package.capnp 的VerifiedInfo结构)。spk unpack my-app.spk [outdir]则在校验签名后将包解压到指定目录(默认去掉.spk后缀作为输出目录名)。spk.h中声明的unpackSpk()与verifySpk()正是这两个操作的底层实现——它们在校验失败时会在写出任何文件之前抛出异常。
发布到应用市场
如果打包了一个很棒的应用,可以查看 应用发布指南,了解如何把应用提交到 Sandstorm 官方应用市场(App Market)。市场会自动处理包 ID 与版本相关事务。
剖析 sandstorm-pkgdef.capnp:包定义的每个字段
spk工具会查找文件中名为pkgdef、类型为PackageDefinition的常量(定义于 package.capnp L29-L70)。仓库中的 meteor-testapp/sandstorm-pkgdef.capnp 是一个带完整注释的真实示例,可作为模板参考。
PackageDefinition 顶层结构
| 字段 | 说明 |
|---|---|
id | 应用 ID 字符串,实际是应用公钥的文本编码(形如h37dm17aa89yrd8zuqpdn36p6zntumtv08fjpu8a8zrte7q1cn60)。通常由spk init自动生成;也可用spk keygen生成新 ID。向spk pack传-i标志可指定备选 ID(适用于做非官方构建、不想用真实私钥的场景) |
manifest | 写入包内sandstorm_manifest的清单,描述应用如何启动与展示 |
sourceMap | 指示从哪里搜索要纳入包的文件 |
fileList | 指向一个逐行列出包内文件路径的文本文件(即sandstorm-files.list);每条路径按"包内位置"书写,经sourceMap映射到源文件 |
alwaysInclude | 无论是否出现在fileList中都必须包含的文件/目录(目录递归包含)。适合收录应用运行中不会打开、但应当随包分发的文件(如 README、版权声明、难以完整测试的运行时依赖) |
bridgeConfig | 使用sandstorm-http-bridge的应用的桥接配置 |
manifest:启动命令、动作与应用元数据
Manifest结构定义于 package.capnp L72-L167,关键字段:
actions:定义"新建文档"处理器。每个Action通过nounPhrase声明创建对象的种类(如文档编辑器创建"document"),UI 中显示为"新建<nounPhrase>";command指定首次启动实例时执行的命令;continueCommand:实例因空闲被关闭后重新启动时执行的命令。可以复用actions中的命令,也可以为两种场景配置不同命令;appTitle:向用户显示的应用名;appVersion:整数版本号,仅用于判断包的新旧(版本更高的包视为更新版本),不必与对外宣传版本号一致;每次发布递增;minUpgradableAppVersion:本包可安全替换的最低旧版本(若历史上有过破坏性数据格式变更,可设为非零);appMarketingVersion:人类可读版本号(如2.9.17),用于展示;minApiVersion/maxApiVersion:应用已知可工作的平台 API 版本区间;metadata:不参与执行、但用于展示与市场分发的数据(见下文)。
Command结构由argv(参数列表,argv[0]为程序名)与environ(环境变量列表)组成。注意两点(package.capnp L112-L131):
- 命令不经过 shell 解释。如果确实需要 shell 展开,必须把 shell 二进制也打进包并显式调用它;
environ定义了应用看到的全部环境——除此之外的环境是空的。仓库示例中myCommand设置PATH=/usr/local/bin:/usr/bin:/bin并导出SANDSTORM=1(应用可据此在运行时检测自己是否运行于 Sandstorm 内,见 meteor-testapp/sandstorm-pkgdef.capnp)。
metadata(package.capnp L301-L535)主要字段:
icons:四种上下文的图标——appGrid(128×128,64KB 上限)、grain(24×24,4KB)、market(150×150,64KB)、marketBig(300×300,256KB);优先使用 SVG 格式;website/codeUrl:应用主页与源码仓库地址(若许可证要求提供源码则codeUrl必填);license:none(默认版权,禁止再分发)、openSource(OSI 批准的开源许可证枚举,如 MIT、Apache-2.0、GPL-3.0、AGPL-3.0 等)、proprietary(专有许可证全文,用户首次使用时需明确同意)或publicDomain;notices用于附带第三方版权声明;categories:应用分类列表(Productivity、Communications、Office、DevTools 等枚举);author:contactEmail(务必填写真实有效的支持邮箱)、upstreamAuthor(若你只是打包了别人的应用)、pgpSignature(用 GPG 对"I am the author of the Sandstorm.io app with the following ID: <app-id>"消息做二进制分离签名,用于在市场中验证作者身份);pgpKeyring:包含上述 PGP 公钥的 GPG keyring 数据;description(Github 风格 Markdown,不允许含 HTML 与图片标签)、shortDescription(1~3 个词,显示在市场网格视图)、screenshots(以"设备无关像素"标注宽高,高 DPI 截图应把宽高填为实际像素的一半)、changeLog。
sourceMap:依赖文件从哪来
SourceMap(package.capnp L169-L193)由若干Mapping组成,每个映射定义:
sourcePath:本地系统中该目录的路径(相对路径相对于包定义文件所在位置解析);packagePath:该目录在包内的目标路径(必须规范、不以/开头;省略则映射到包根目录);hidePaths:映射时隐藏的文件/子目录名列表(仅允许规范路径,不得使用.、..或前导/)。
spk init默认生成两条映射:(sourcePath = ".")优先搜索当前目录,然后(sourcePath = "/")搜索系统根目录并隐藏home、proc、sys等敏感路径。多目录会以"合并"方式工作——若多个 sourcePath 都包含同一目标路径,pack会合并这些目录的内容,这正解释了"源码树中放etc/foo即可覆盖系统/etc/foo"的行为(spk.c++ L1272-L1310)。
fileList 与 alwaysInclude:两种文件收集模式
- 动态收集模式(默认):
fileList = "sandstorm-files.list",由spk dev观察运行中的应用自动维护。优点:上手快;缺点:清单可能因测试不充分而不完整; - 全量包含模式:
alwaysInclude = ["."]配合-A/-I使用,把指定目录下所有文件原样打入包。spk pack时若alwaysInclude含.,则文件清单不再重要(spk.c++ L2158-L2166)。这种方式更接近"确定性构建",适合需要精确控制包内容的场景。仓库中的 meteor-testapp/sandstorm-pkgdef.capnp 正是使用alwaysInclude = ["."]的全量模式示例。
bridgeConfig:HTTP 桥接与权限角色
BridgeConfig(package.capnp L195-L299)专为使用sandstorm-http-bridge的应用设计:
viewInfo:定义可共享的权限(permissions)与角色(roles)。每次请求时,桥接进程会把用户当前拥有的权限以逗号分隔的列表写入X-Sandstorm-Permissions请求头;角色则显示在共享 UI 中。注意:权限列表只能追加,绝不能重排或删除已有字段,否则会改变既有实例的行为与权限(仓库示例 meteor-testapp/sandstorm-pkgdef.capnp 定义了一个editor权限和 editor/viewer 两个角色);apiPath:所有 API 请求(经 API 端点进入的请求)的前缀路径,必须以/结尾。它只用于让 API URL 与 UI URL 分离,不会限制 API 令牌持有者的访问范围——真正的访问控制必须通过权限并在 UI/API 两侧同时强制实施(参见 HTTP API 文档);saveIdentityCaps:为true时,新用户首次访问实例,桥会保存其身份能力,后续可通过getSavedIdentity获取(做通知类功能通常需要它);expectAppHooks:为true时,桥期望应用通过/tmp/sandstorm-api建立 Cap'n Proto 连接并实现AppHooks,用于动态生成getViewInfo()或导出额外的持久能力;powerboxApis:声明应用通过 powerbox 对外导出的 HTTP API(含名称、展示信息、路径前缀、tag与所需权限)。
签名、密钥环与应用 ID
密码学机制
你的应用包使用Ed25519算法做加密签名(底层是 libsodium 的crypto_sign,见 spk.h 与 package.capnp L689-L720)。公钥就是应用的全局 ID——所有用同一把密钥签名的包,都被视为同一应用的不同版本(spk pack输出中会打印 app ID)。spk init会为新应用自动创建一对密钥。
密钥环文件
对应的私钥被放入你的 Sandstorm 密钥环,默认存储在$HOME/.sandstorm-keyring(spk.c++ L394-L400;可用-k <path>选项改用其他密钥环文件)。这个文件必须妥善保管:
- 丢失它:你将无法再为应用构建更新版本;
- 被窃取:攻击者可以发布你的应用的更新包。
spk的密钥相关命令:
| 命令 | 作用 |
|---|---|
spk listkeys | 列出密钥环上的所有密钥对应的应用 ID |
spk getkey <app-id> | 从密钥环中取出指定 ID 的密钥,以 Cap'n Proto 消息形式输出到 stdout(spk help getkey查看用法) |
spk keygen | 生成新的应用 ID 与签名密钥并存入密钥环(新建应用时更推荐用spk init,keygen适用于更换密钥的场景) |
密钥环合并:直接把两个密钥环文件用cat拼接即可合并(cat keys >> ~/.sandstorm-keyring,这也是spk getkey帮助信息中推荐的把收到的密钥加入自己密钥环的方式)。因此可以只把特定应用 ID 的密钥导出(spk getkey)发给发布负责人。
开发模式不需要私钥:spk dev在本地服务器注册应用时,服务器直接信任你给出的 ID(反正你对本地服务器有完全控制权),因此私钥不需要分发给每个开发者——只有负责构建发布版本的人需要持有密钥。
已知限制:目前密钥环未加密,因此对任何在你用户账号下运行的软件都可见。这在将来会改进;但一般来说,如果恶意软件能以你的身份运行,问题就已经很严重了。
包 ID(数据包 ID)
如需获取某个包的数据包 ID,可运行:
sha256sum package.spk | head -c 32; echo即取包文件 SHA-256 哈希的前 32 个十六进制字符。应用市场上线后这部分通常已自动处理,手动计算已不再是重点。
.spk 文件内部格式
一个.spk包在磁盘上是这样组织的(package.capnp L700-L752):
- 8 字节魔数(
\x8f\xc6\xcd\xef\x45\x1a\xea\x96)——若未来包格式破坏兼容性,此魔数会改变; - 后续全部内容经XZ 压缩(
spk pack实际调用系统xz --threads=0 --compress --stdout,见 spk.c++ L1012-L1016); - 压缩流内是两个 Cap'n Proto 消息:
Signature:publicKey(32 字节 libsodium 签名公钥,其文本形式即应用 ID)与signature(对归档部分 SHA-512 哈希的签名);Archive:文件树(files列表),每个文件记录名称、纳秒精度修改时间,以及内容(regular / executable / symlink / directory 四种类型)。
spk verify正是解压后校验该签名并输出应用 ID;spk unpack则在签名有效的前提下把文件树落地到目录。
进阶技巧与最佳实践
访问外部资源
默认情况下,应用没有任何网络访问权,包括服务器端——它只能响应来自用户的 HTTP 请求。如果需要访问外部世界(发起出站 HTTP 请求、收发电子邮件等),必须通过 Sandstorm 的 API 申请相应能力。各类外部资源(出站 HTTP、邮件等)的接入指南见完整开发者文档。
可复现构建
上述"直接从开发机复制文件"的方式适合快速启动,但不利于长期维护。当项目变得正式后,应建立密闭(hermetic)构建环境,让其他开发者可以轻松复现构建。
方法是编辑sandstorm-pkgdef.capnp,通过修改sourceMap让spk不从真实主机系统、而从你自己准备好的其他目录查找文件。例如在独立目录中搭建一个干净的 chroot 环境,然后把sourceMap指向那里。chroot 环境的搭建细节超出本文范围,属于通用的、与 Sandstorm 无关的成熟技术。
测试升级兼容性
当应用后续发生改动时,可以用spk dev针对旧版本产生的既有数据做升级测试:任何时候运行spk dev,开发版应用都会临时覆盖已安装的版本——包括打开既有实例的场景。这让你可以在真实数据上验证升级路径,而无需先卸载旧版本。
什么样的应用适合 Sandstorm
并非每个 Web 应用都适合做成 Sandstorm 应用。Sandstorm 面向的是数据逻辑上归用户所有的应用:每个实例由单个终端用户拥有(而非应用开发者),用户可以与他人共享并协作,但实例最终归属于个人。
适合的场景:
- 文档编辑器、电子表格等内容创作工具;
- 邮件 / 聊天 / 通讯应用;
- 日历、待办清单、个人任务管理;
- RSS 阅读器;
- 个人文件 / 媒体存储;
- 博客(含微博客);
- 个人主页;
- 联邦式社交网络。
不适合的场景:
- 公共搜索引擎;
- 新闻门户;
- 大型讨论论坛(不过"每个用户拥有自己创建的帖子线程"的联邦式论坛可以成立);
- 内容分发服务;
- 在线商店;
- 中心化社交网络。
用户可以为安装的任何应用创建多个实例。默认情况下每个实例相互隔离、可独立共享。应用应把实例粒度设计到"共享"有意义的最小单元:例如文档编辑器应让每篇文档对应一个独立实例,这样用户就能借助 Sandstorm 平台的共享功能来分享文档访问权。纯客户端应用(Unhosted 风格)在这点上尤其简单——把"保存/加载位置选择"删掉,改为自动保存到/var下的固定路径即可(参见纯客户端应用指南)。
常见问题与更多资料
- 打包过程中卡住时,先查 打包故障排查;
- 想深入理解 Sandstorm 应用设计的高层问题,读 App 开发者手册;
- 涉及认证、权限、HTTP API、通知、调试等主题的完整文档,见开发者文档总目录。
最后,如果你打包出了酷炫的 Sandstorm 应用并希望更多人用上它,请按照应用发布指南提交到官方应用市场——带上spk原始打包学到的这层理解,你会比大多数打包者更清楚自己的包里装的是什么。
- 后端
- 容器运行时
- 安全
- 云原生
【免费下载链接】sandstorm
Sandstorm is a self-hostable web productivity suite. It's implemented as a security-hardened web app package manager. | Actively sponsored by our friends at TestMu AI
相关推荐
Moya响应式请求终极指南:RxSwift、ReactiveSwift与Combine三大扩展实战对比
Moya响应式请求终极指南:RxSwift、ReactiveSwift与Combine三大扩展实战对比 Moya 是一个用 Swift 编写的网络抽象层库,它基
后端容器运行时安全云原生Sandstorm 打包定制实战:深入理解并自定义 vagrant-spk 的 `.sandstorm` 目录
Sandstorm 打包定制实战:深入理解并自定义 vagrant spk 的 .sandstorm 目录 vagrant spk 是 Sandstorm 官方
后端容器运行时安全云原生Johnny-Five 湿度传感器 DHT11 I2C Nano Backpack 接入指南:Hygrometer 控制器用法与固件原理
Johnny Five 湿度传感器 DHT11 I2C Nano Backpack 接入指南:Hygrometer 控制器用法与固件原理 本文以 Johnny
后端容器运行时安全云原生
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考