# Drone 多平台打包与 IPFS 发布 当前 `.drone.yml` 不再构建或推送 Docker 镜像,也不依赖镜像仓库账号密码。流水线依次编译 linux/amd64、linux/arm64、windows/amd64、windows/arm64、darwin/amd64、darwin/arm64,为六个平台保留独立压缩包,逐个上传并请求 pin,再发布一个四文件的下载索引目录。 流水线保留原有头部和代理,不添加 `type`、`platform` 或顶层 `trigger`;源码改由 Drone 在步骤执行前默认克隆,不再维护自定义 clone 步骤。`package` 步骤内完成打包后直接执行 IPFS 上传,让最终下载地址直接显示在打包日志末尾,不再按构建事件跳过上传。`Dockerfile` 不参与此流水线,原有本地 Windows 单 EXE 打包入口保持不变。调整后的克隆与触发结果仍需在实际 Drone 环境验证。 ## 流水线与平台包 编译先执行一次 `go mod download`,再串行构建六个平台。流水线和脚本默认均为 `BUILD_JOBS=1`(同一时间一个平台)、`GO_BUILD_PARALLELISM=1`(每平台 `go build -p 1`),默认 `GOMAXPROCS=1`。前两者分别允许 1–6、1–16,显式增加并发需观察容器内存。任何平台失败都会等待同批任务结束后退出,不生成成功元数据。不改流水线头部或触发条件,不增加 privileged/宿主机挂载;未配置跨构建缓存,新的临时工作区仍可能重新下载依赖。实际提速幅度需同一 runner 冷/热缓存对比,不能按并发数推定。 1. 使用 Drone 默认克隆取得当前构建源码;不再启动 Alpine 容器安装 Git、写入 `.netrc` 或手工补取和校验提交。 2. 使用 Node 22.19.0 执行前端生产构建。 3. 使用 Go 1.25.0、`CGO_ENABLED=0` 依次编译六个平台,内嵌前端页面及版本、完整提交 ID、构建时间。 4. 执行发布脚本测试,并用 Linux amd64 程序在隔离临时数据库和端口上验证版本、启动、网页资源及登录,然后生成六个独立压缩包。 5. 在同一 `package` 步骤中,打包成功后直接上传 IPFS,并打印最终下载地址。所有进入此步骤且通过测试、验证和打包的构建都执行上传,包括手动构建、PR 和未设置事件变量的构建。上传失败会使 `package` 步骤失败,不打印成功下载汇总。按用户要求,令牌直接配置在 YAML 中,不依赖 Drone Secret。 | 系统 | 架构 | 文件名 | | --- | --- | --- | | Linux | amd64 | `linkport--linux-amd64.tar.gz` | | Linux | arm64 | `linkport--linux-arm64.tar.gz` | | Windows | amd64 | `linkport--windows-amd64.zip` | | Windows | arm64 | `linkport--windows-arm64.zip` | | macOS | amd64 | `linkport--darwin-amd64.tar.gz` | | macOS | arm64 | `linkport--darwin-arm64.tar.gz` | 二进制保存在 `bin/build-cache/ci-release/<系统>-<架构>/`;六个压缩包保存在 `bin/releases/ci/`。已有输出目录会使打包失败,不会自动清理之前的产物;Drone 应使用新的构建工作区。 恢复 Linux、Windows、macOS 的 amd64/arm64 全部六个目标,已有旧包不删除。 每包包含 `linkport` 或 `linkport.exe`、`VERSION`、`BUILDINFO.json`、二进制 `SHA256SUMS.txt`、README 和构建/测试文档。顶层另保留 `release.json`(当前平台清单、版本、提交及校验值)和 `SHA256SUMS.txt`(所有压缩包及清单的校验值)。不打包真实 `data/`、数据库、密钥、源码、node_modules 或仓库凭据。 tag 必须为 `v`,例如 `VERSION=0.3.0` 对应 `v0.3.0`;不匹配时失败。交叉编译不代表六个平台全部完成原生运行验证;CI 仅运行 Linux amd64 冒烟,不连接真实上游数据库或 SSH 服务。 ## IPFS 配置与下载 成功日志末尾以 `=== IPFS upload complete: download URLs ===` 集中列出下载总页、六个包和四份索引/清单的完整地址;每个 HTTP(S) URL 独占一行,便于复制。当前配置使用 `https://ipfs.freeicu.top/ipfs/`;原始 IPFS URI 放在此汇总之前。 `package` 的 `IPFS_API` 固定为 `https://ipfs.freeicu.top`,不再要求 `ipfs_api` Secret。 此节点要求认证。按用户要求,`IPFS_TOKEN` 直接配置在 YAML 中,不依赖 Drone Secret。仓库读者及 Git 历史读者可获取令牌,应限制访问并在泄露时轮换,不要在其他文档或日志中重复令牌。 上传日志在每次请求前显示包序号、总数和文件名,随后显示 add 接受结果;这只表示请求了 pin,并非独立确认 pin 状态。六包和索引全部成功后输出同源网关的 `Download page` 地址,以及六个平台包、四个索引/清单文件的 HTTP(S) 下载直链,保留原 `ipfs://` URI。包直链使用独立包 CID,并附带 filename 参数保留文件名,不误拼到索引目录下。失败时不输出最终下载页或直链。API 所在域名需同时提供 `/ipfs/` 网关,脚本不额外探测该地址。 上传只使用 `/api/v0/add`,显式发送 `User-Agent: linkport-ci-release/1`。保留代理环境和 TLS 证书校验;Bearer token 仅通过 curl stdin 配置传入,不进入命令行参数或日志,禁用隐式 curl 配置以免本机配置改变安全选项。 1. 先核对全部六包、平台元数据与原始校验清单;校验失败不发起上传。 2. 六个 archive 各发送一次 multipart 请求,均设置 `pin=true`、`cid-version=1`、`wrap-with-directory=false`,校验响应恰好包含对应文件 Name 和有效 CIDv1。每个包分别请求 pin,并保留各自 CID。 3. 生成 `ipfs-index.json`,包含 `version`、`commit`、`buildTime` 和 `packages` 数组;每包包含 `os`、`arch`、`file`、`cid`、`sha256`、`uri`,其中 URI 为 `ipfs://<包CID>`。 4. 生成安全转义的 `index.html`,列出六包及 SHA256。包下载链接使用绝对网关路径 `/ipfs/<包CID>?filename=`,不使用 `../`;页面同时提供 `release.json`、`SHA256SUMS.txt` 和 `ipfs-index.json` 相对链接。 5. 将 `index.html`、`ipfs-index.json`、原 `release.json` 和原 `SHA256SUMS.txt` 四个小文件一起上传,设置 `wrap-with-directory=true`、`pin=true`、`cid-version=1`,验证四个文件 Name/CID 和索引目录 CID。完整发布共七个 add 请求(六包各一次,加一次四文件索引请求)。 生成文件及两份原清单的上传副本放在受检的系统临时目录,索引请求完成或失败后清理;原六包及两份清单始终保留、不改写。任何请求失败、响应缺项/重复/无效 CID 都使发布失败,不写最终成功回执。可输出已完成单包的 CID,便于诊断;部分成功不代表全部发布成功,也不会自动撤销已请求的 pin。已有最终回执时,在网络请求前拒绝覆盖,请保留旧回执并使用新的发布目录。 全部成功后写入 `bin/releases/ci.ipfs.json`:`{ cid, files }` 的 `cid` 是**索引目录 CID**,`files` 共十项。前六项为完整包索引条目,URI 是各自 `ipfs://<包CID>`;后四项为 `{ file, uri }`,URI 为 `ipfs://<索引CID>/<文件名>`。这是相对旧八文件同目录方案的发布格式变化,消费者不能再假设 `<索引CID>/<包文件名>` 存在;没有应用配置或数据库迁移。 纯函数 `createIpfsIndex(manifest, archiveEntries)` 从 `scripts/ci-release.mjs` 导出,返回 `{ index, html }`,不读写文件、不访问网络、不修改输入。`manifest` 为六平台 `release.json` 对象,脚本不接受临时单包或三包 manifest;`archiveEntries` 必须恰好包含六项 `{ file, cid, sha256 }`,函数校验名称、平台、校验值及 CID,再从 manifest 补齐平台信息。修改目标范围时需同步调整编译脚本和发布脚本的目标列表及测试。 **pin 与备份边界:**索引文本中的包 CID/URI 不是 IPFS DAG 链接。pin 索引目录只保留四个索引/清单文件,不会递归 pin 六个包;六个包依靠各自 add 的 `pin=true` 请求保留。迁移或备份必须保存并处理全部六个包 CID 和索引 CID(共七个 CID),不能只备份索引 CID。脚本确认 add 成功响应,不额外查询 pin 状态。节点 pin 不等于公共网关完整可用,也不等于备份;Drone 临时工作区可能被回收。 ## 历史节点验证状态(2026-09-24,旧六平台) 以下保留旧六包方案的历史实测记录,不代表当前代码已完成真实上传验证;已有旧包不删除。 据当时的实测记录,`/api/v0/add` 不带认证返回 401,带认证空文件请求返回 400 `file argument 'path' is required`。`/api/v0/version` 使用应用 User-Agent 返回 200,而 Python-urllib 默认 User-Agent 被 Cloudflare 1010 拦截;此前的 403 不代表上传不可用。`object/new`、`files/stat`、`dag/put` 均返回 404,发布实现不依赖这些组合目录接口。 117MB 八文件整批上传连接中断,单个约 18MB 包上传返回 HTTP 200,因此改用小请求逐包发布;尚未确定 Cloudflare 的具体大小上限,也不认定中断原因已被证实。当前六个 archive 已逐个上传 HTTP 200,均请求 `pin=true`;`/api/v0/pin/ls?arg=<本次CID>` 返回 404,无法独立查询 pin 状态,不能声称已独立确认递归 pin。 该域名公共网关对六包的首 1024 字节 Range 请求均返回 206,内容与本地一致;完整下载曾中途 `IncompleteRead`,主代理正在分块校验,不能据此声称完整公网下载已验证。索引目录发布及最终真实 CID、完整下载校验结果由主代理补充。本次脚本测试使用本机 HTTP fixture;只读调用纯函数验证真实进度 JSON 可生成六包索引,不发起真实网络请求或修改 bin 产物。 ## 网络与克隆 流水线不设置 HTTP/HTTPS 代理;软件源、Git、npm、Go 依赖和 IPFS API 均由构建环境直接访问。 源码由 Drone 默认克隆阶段直接取得,其网络、仓库凭据和构建提交检出由 Drone 负责;流水线不再关闭默认克隆,也不再自行处理 `DRONE_NETRC_*`、创建认证文件或补取提交。自定义克隆原有的 SHA-1/SHA-256 协商和提交一致性检查不再由仓库脚本单独保证。 宿主机 Docker daemon 在流水线启动前拉取工具镜像,也需能够直接访问镜像仓库。仓库配置不会修改宿主机网络设置。 没有构建记录时检查 Drone 仓库启用、Gitea webhook 和 runner;源码克隆失败时检查 Drone 默认克隆日志、仓库集成以及 runner 到 Git 服务的网络;已经生成包但发布失败时检查 `package` 日志中的上传部分、令牌配置和 Kubo API 访问权限。