发布 videolabs 网站
“发布”意味着整个站点每次部署都会包含所有实验室。
站点仓库:/opt/opus/videolabs (github.com/bigzaqui/videolabs,推送到 main 时部署到 https://videolabs.pages.dev;GitHub Actions 发布 site/ 文件夹)。
实验室:/opt/opus/videolabs/labs/<slug>/(每个实验室一个文件夹,位于同一仓库;slug 是 MP4 文件名 out/<slug>.mp4)。它们通过 videolabs_create-video 技能构建(node engine/cli.mjs <slug> ...)。
视频存储在私有 R2 存储桶 videolabs 中,路径为 <slug>/<slug>.mp4,通过 /media/<slug>/<slug>.mp4 提供服务,该路径受 Turnstile 会话保护(没有会话的请求返回 401)。
凭据:/opt/opus/videolabs/.env(由脚本加载;模板为 .env.example)。切勿打印、复制或提交密钥。GitHub 仓库是公开的(用户对此可以接受,因为旁白音频、脚本、截图和实验室输出都将公开,并且不介意已销毁实验室的临时密码出现在那里),因此任何文件中都不能出现真正的凭据。此技能的源代码位于仓库的 skills/videolabs_publish/ 中,通过 scripts/setup.sh 链接到 ~/.claude/skills。
实验室需要的内容(由脚本检查)
out/<slug>.mp4(快速启动:moov在mdat之前;使用ffmpeg -i in.mp4 -c copy -movflags +faststart out.mp4修复),out/player/chapters.json(node engine/cli.mjs <slug> player),out/spot.jpg(海报;使用ffmpeg -ss 425 -i out/<slug>.mp4 -frames:v 1 -q:v 3 out/spot.jpg抓取一个图表帧)。- 可选
out/player/lab.json(当实验室有lab/lab-meta.json、lab/*.sh、lab/captures/时,来自node engine/cli.mjs <slug> player)。如果存在,site/lab.js会在实验室页面上显示实验室资源。如果截图或lab-meta.json发生变化,请先重新运行node engine/cli.mjs <slug> player。 labs/<slug>/lab.config.json包含title、description和tags。脚本会据此添加或更新site/labs.json中的条目(并填充minutes和lessons),因此切勿手动编辑目录。对于新实验室,如果脚本中明确,则使用其标题/描述/标签;否则询问用户。切勿静默地凭空编造它们。
步骤
- 计划
cd /opt/opus/videolabs && scripts/publish-all.sh --dry-run。它会为每个实验室打印一行PLAN(将上传或跳过什么,是否有lab.json),为无法发布的实验室打印ATTENTION行(缺少文件、不是 fast-start、没有lab.config.json),以及为没有项目的目录条目打印NOTE行。退出代码 2 表示存在至少一个ATTENTION。逐一修复每个问题(编写lab.config.json,运行node engine/cli.mjs <slug> player,制作海报……)并重复,直到没有错误,或告知用户哪个实验室被遗漏以及原因。用几行向用户展示计划,包括将上传哪些视频以及它们的大小。- 没有已记录的上传(缺少
state/<slug>.sha256)的实验室将被上传。如果用户说 R2 中已有完全相同的文件,请使用--adopt <slug>记录其哈希值而不是上传。--force-upload会重新上传所有内容;仅在用户要求替换视频时使用(重新上传后,查看者可能最多会看到 24 小时的缓存)。
- 没有已记录的上传(缺少
- 同步
scripts/publish-all.sh(相同的标志)。对于每个准备好的实验室,它首先将渲染压缩为out/<slug>.web.mp4(scripts/compress-mp4.sh:单声道 AAC 64k + x264 CRF 28,大小约减少 65%,每 12 分钟视频约需 5 分钟,当渲染未更改时会缓存;渲染本身永不修改),然后如果发生更改,将该 Web 副本上传到 R2,根据lab.config.json添加/更新目录条目,复制chapters.json、poster.jpg、lab.json(当实验室没有时,从站点文件夹中移除),为新实验室创建页面index.html(使用共享模板,包含标题和描述),在state/<slug>.sha256中记录视频哈希,并刷新site/labs.json中的minutes/lessons。上传在此处完成,先于任何推送,因此页面在没有视频的情况下永远不会上线。可以安全地再次运行。 - 审查和提交 首先运行
scripts/check-secrets.sh(如果.env中的值出现在任何文件中,如果某个文件为 API 密钥、令牌或密码变量分配了值,或者存在私钥,则会失败)并修复其报告的问题,然后再继续。向用户展示git status和 videolabs 仓库的 diff 摘要(新增/更改的实验室、labs.json、state/以及对共享播放器代码的任何更改,见下文)。如果实验室回答了 GitHub 视频请求(标记为video-request的 issue),请在提交消息正文中添加Closes #N,以便推送到main时关闭该 issue。提交所有内容,包括更改后的labs/*/audio/(消息类似于Publish labs: ceph-first-steps (new), kafka-in-a-nutshell (lab page),并包含会话归属提醒中的 Co-Authored-By 行)并执行git push origin main(已设置 SSH 远程)。如果工作树中对共享播放器代码的更改不属于此请求,请先询问是否包含它们。 - 部署 等待 GitHub Actions 运行完成(
curl -s https://api.github.com/repos/bigzaqui/videolabs/actions/runs?per_page=1,检查status/conclusion;大约每 20 秒轮询一次,最多约 3 分钟)。如果失败,报告它并且不要盲目重试;缺失密钥的错误意味着需要处理仓库密钥CLOUDFLARE_API_TOKEN/CLOUDFLARE_ACCOUNT_ID。 - 验证
site/labs.json中的每个实验室(不仅仅是新的):/返回 200 并列出它们;/labs/<slug>/返回 200;/labs/<slug>/chapters.json返回 JSON(不是 HTML 后备方案:站点对未知路径返回 200,因此请检查内容类型或正文);/labs/<slug>/lab.json为拥有该文件的实验室返回 JSON;/media/<slug>/<slug>.mp4在没有会话时返回 401(证明网关已启动)。播放本身需要带有 Turnstile 检查的浏览器会话,因此说明它未被播放。报告 URL:https://videolabs.pages.dev/labs/<slug>/。
要仅发布一个实验室(很少见),运行 scripts/publish-lab.sh <lab-dir> <slug> "<title>" "<description>" 并手动更新 site/labs.json,然后继续执行步骤 3。
共享代码
site/labs/kafka-in-a-nutshell/index.html 是每个新实验室复制的页面模板(标题、标题行和描述在运行时来自 site/labs.json),site/lab.js 在实验室有 lab.json 时渲染实验室资源,site/gate.js 是 Turnstile 检查。更改它们会影响下次推送时的每个实验室,因此请首先对照现有实验室检查它们。现有实验室会保留自己的 index.html 副本;模板更改只有在它们的副本也更新时才会影响到它们。