- contract/checklist/AGENTS template cover connect/script/worker/img/media - check-app.sh validates directives and cross-origin loads against declarations - selftest covers external worker/script and blob worker cases
10 KiB
发布、部署与验收(release-cli / 商店 / quickstart)
1. release-cli(aigrego-release)
零依赖单文件 ESM(Node ≥18),不上 npm。获取:
# 一行安装(分发仓随平台发版同步)
curl -sSL https://code.innev.cn/images/tickshield-dist/raw/branch/main/cli/aigrego-release.mjs \
-o /usr/local/bin/aigrego-release && chmod +x /usr/local/bin/aigrego-release
# 或 vendored 进应用仓库:node ./tools/aigrego-release.mjs ...
# CI runner 无 node 时:docker run --rm -v "$PWD:/src" node:20-alpine node /src/cli/aigrego-release.mjs ...
命令集(仅四个)
| 命令 | 作用 |
|---|---|
whoami |
用应用凭证换一次服务态 TDT 验凭证——CI 里先跑这个(放在构建之前) |
publish |
四个可组合子动作,固定顺序:--manifest → --dist → --image → --store |
register-app --manifest m.json [--host --port] |
调 POST /api/register,网关 ≤2s 热载 /svc/<key>/ |
status |
查状态机/生效版本/在审条目 |
publish 子动作:
--manifest app.manifest.json→POST /api/apps/submit。治理字段有 diff ⇒ 202 进审核队列,批准前不影响已安装租户;无 diff ⇒ 200 当场生效;首个版本直接生效。版本号以 manifest.version 为准(semver)。--dist <dir|tgz>→POST /api/apps/:key/artifacts。产物目录根的index.html必须存在(tar czf -C <dir> .,套一层目录会被拒);tgz 请求体 ≤64 MiB(网关同值,超过直接 413)、路径穿越/symlink 被拒、同卷 rename 原子替换;上传后/apps/<key>/*托管立即可读(2026-10 起拍平,旧/_/子路径产物必须重新构建上传)。超限走下方「大产物外置」。--image <ref>→POST /api/apps/:key/deploy。只写deployDescriptor.image并滚动重启;镜像必须先由应用方自己 push 到平台白名单 registry;docker.sock 不可用时降级为仅记录。--store→POST /api/apps/:key/publish自行上架。前置:平台已给该应用开auto_publish,否则 403;已上架返回 409 视为跳过。
大产物外置(dist tgz > 64 MiB)
- dist 只保留前端壳(index.html + 它直接引用的 JS/CSS),核心资源放
assets/目录(不进 dist)。 - CI 用钉版
minio/mc镜像 mirror:apps/<key>/<version>/,版本化路径 + immutable 缓存;旧版本保留供回滚。 - 桶配 CORS:
GET/HEAD/OPTIONS、Allow-Headers: Range、Expose-Headers: Content-Length,Content-Range,ETag;CI 做匿名读自检。 - manifest 按指令声明(仅 micro,治理字段 → 202 审核):
来源只能是
"embedCsp": { "connectSrc": ["https://static.example.cn"], "scriptSrc": ["blob:", "https://core.example.cn"], "workerSrc": ["blob:"], "imgSrc": ["blob:"] }blob:或scheme://host[:port];platform 基线已固定script-src 'self' 'wasm-unsafe-eval'(wasm 编译必需)。 - 指令对应关系:核心用
fetch加载 →connectSrc;外链/动态脚本与 Worker 内importScripts→scriptSrc;new Worker(含 blob)→workerSrc;blob 图片/音视频 →imgSrc/mediaSrc。外链 stylesheet 仍未放开。 - 模板见
references/templates/gitea-app-release.yaml的assetsjob(未配TS_ASSETS_BUCKET时自动跳过)。
凭证与配置
复用应用服务凭证(X-App-Key/X-App-Secret),与容器自注册、/oauth/token 同一套,无独立发布令牌。来源优先级:参数 > 环境变量 > 配置文件。
AIGREGO_PORTAL_URL/--portal:门户地址(随环境变)APP_KEY/--app-key:就是 listingKey 本身APP_SECRET/--app-secret:平台签发,仅此一次明文可见。CI 里优先环境变量——命令行参数会进ps和构建日志- 配置文件(
--config/$AIGREGO_REGISTRY_CONFIG/./.aigrego-registry.env)只读LISTING_KEY;出现APP_SECRET会告警并忽略(密钥不落盘;文件进 .gitignore)
越权语义:应用只能操作自己(--key 与凭证 listingKey 不一致 → 403);CLI 能写的 deployDescriptor 字段只有 image。
退出码与门禁
0 = 全部提交被接受(含治理变更 202 待审,非阻塞成功);1 = 认证/越权/校验拒绝/--wait 超时被驳回。--wait [秒](默认 1800)轮询等审批落地,把"审批通过且生效"纳入 CI 门禁。
CI 模板
照 references/templates/gitea-app-release.yaml:推 tag v* → buildx 多架构推镜像(tag = 去 v 版本号 + latest)→ publish --manifest --image --store --wait 600。仓库 secrets:APP_KEY/APP_SECRET/REGISTRY_USER/REGISTRY_TOKEN;manifest.version 与 tag 对齐。
2. 商店部署链路(APPSTORE-RUNTIME)
状态机 draft → published → installed → active。开发者交付物 = 镜像 + manifest;注册/上架/授权/安装/启停/卸载全部由平台在 console「应用市场」完成(租户侧注册/安装/卸载端点已下线)。
- manifest 进平台双通道:
publish --manifest(CLI/CI);或平台侧 console「应用中心」按向导导入app.manifest.json——同一条 submit 管道:同一套校验、治理字段变更同样 202 进审核队列;两条路消费的都是应用仓库里这份文件 - 上架双通道:平台管理员 console 操作;或应用凭证 + auto_publish(
publish --store) - 授权即安装:授权变更自动建/停
app_installs行;收权停用(数据保留);卸载回 published,安装行保留 - FR-101 registries 表即白名单:安装按镜像 host 精确匹配 registries 行,未登记一律 403;空表 = 全拒(安全默认)。host 归一对齐 docker 语义:首段含
./:或为localhost→ 首段即 host;否则归一docker.io - 安装 API 收窄:只收
{image, port, env}(全可选;image 回退 deployDescriptor.image,port 默认 8080);容器 spec 固定模板渲染,不暴露卷挂载/privileged/cap-add/network-mode - 安装等待:容器起后 30s 内必须完成自注册,否则 op failed(容器保留)——镜像必须基于
tickshield-node-runtime:base内置注册循环 - 启停 = 容器层 + 路由层墓碑/恢复;卸载 = 摘流 → Stop → Remove(可选 removeImage)→ 回 published
- 全部操作审计留痕;docker.sock 缺失时运行时端点 503
平台侧待办清单(Agent 做不到,交付给用户/平台管理员):
- console「应用市场」新建应用 → 签发
APP_KEY/APP_SECRET(仅此一次明文) - 应用详情开
auto_publish(要走--store自助上架时) - console「仓库管理」登记镜像 host(私有仓库配凭证,可「测试登录」)
- 治理字段变更后的审核批准
- 租户授权(默认无 grant 行 = 全量授予)
3. 接入门验收清单(NFR-5 六项)
平台出口验收基准(scripts/verify-onboarding.sh,按其样式改写演练路由):
| # | 项 | 通过标准 |
|---|---|---|
| 1 | Manifest 完整性 | 缺字段 manifest → 400;真实 manifest submit → 200 且 effective:true |
| 2 | 四道闸 | 匿名 → 401;跨 aud 经网关 → 401;跨 aud 直连 → 403 gate=audience;无 scope → 403 gate=scope;member 调 admin → 403 gate=role;无页面权限 → 403 gate=acl;合法 → 200 |
| 3 | 租户隔离 | 跨租户读 = 空集;本租户写入后他租户不可见 |
| 4 | SDK 握手 | micro 应用宿主桥全流程(托管/host 页/握手/acl 显隐/callService) |
| 5 | 制品交付 | dist 上传/manifest 提交/register/镜像发布全链路 |
| 6 | 健康检查 | /health 恰为 {"status","db"} 两键且均 "ok"(直连与经网关均 200) |
4. 上线前验收矩阵(分层)
参照平台既有应用(secret-manager)的出口门:
- 接入层:注册生效且心跳鲜活;
/health规范;manifest 逐字一致且不含 deployDescriptor;四道闸矩阵全绿(含伪造签名 401、错 aud、服务态写 403、无租户上下文 403);跨租户空集语义 - 功能/安全层:业务 CRUD 与错误码;密文/敏感值零命中日志与审计;trace_id 端到端串联
- 生态互操作层:跨应用 token-exchange(未声明 exchangeTargets → 拒绝;声明 + consent 后仅本租户可取)
- 发布演练层:
publish --dist真实产物上传回读托管;--manifest非治理字段直接生效 / scopes 变更进审核队列;演练幂等可重跑 - 商店全链路:建应用签凭证 → 凭证 submit(draft 对租户不可见)→ auto_publish 关时凭证上架 403 / 开后直上 / 重复上架 409 → 授权语义(关授权即隐藏)→ 运行时安装(容器在跑 + 实例 active +
/svc/<key>/health200 + 授权租户自动有安装行)→ stop 摘流 / start 恢复 → 收权 disabled → 卸载回 published + 容器删除
5. quickstart catalog 物料(进平台服务目录时)
向平台仓 deploy/quickstart/catalog/apps/ 提交一对同名文件:
<key>.yaml(compose 片段):必须含顶层services:与networks:;container_name== listingKey;networks 挂平台网且 alias == listingKey;image 用${TS_IMAGE_PREFIX:-...}/tickshield-<key>:${TS_<KEY>_IMAGE:-${TS_VERSION}}形态;environment 至少PORT/PORTAL_API_URL/APP_KEY/APP_SECRET(secret 由 install.sh 统一生成落 .env,幂等复用)+ 业务 env 复用平台账本项;micro 应用挂门户托管产物卷(只读);禁止出现 docker.sock(静态门禁)<key>.json(app manifest):listingKey/name/version必填,内容照 manifest 契约;安装时拷入data/manifests/由 portal ScanManifests 建应用行(凭证经APP_SECRET_LINES注入),容器自注册建实例
6. 版本/tag 约定
git tag v<x.y.z> 触发 CI;镜像 tag = 去 v 的版本号 + 另挂 latest;manifest.version 与 tag 同步且 semver。latest_version 只在 semver 不减时拨动——回滚 = 显式发新版本号,不能靠重启旧容器。