name: videolabs_publish description: Publish the whole videolabs site (https://videolabs.pages.dev): sync every lab in the repo's labs/ folder into the site (video to Cloudflare R2 only when it changed, player assets, lab resources, catalog), commit and push the videolabs repo so GitHub Actions deploys to Cloudflare Pages, then verify every lab. Use when the user wants to publish, deploy, add, update or re-upload video labs to videolabs.pages.dev. One run publishes all labs, not just one.
Publish the videolabs site
"Publish" means the whole site is deployed with all labs, every time.
Site repo: /opt/opus/videolabs (github.com/bigzaqui/videolabs, deploys to https://videolabs.pages.dev on push to main; GitHub Actions publishes the site/ folder).
Labs: /opt/opus/videolabs/labs/<slug>/ (one folder per lab, in the same repo; the slug is the MP4 name out/<slug>.mp4). They are built with the videolabs_create-video skill (node engine/cli.mjs <slug> ...).
Videos live in the private R2 bucket videolabs at <slug>/<slug>.mp4 and are served through /media/<slug>/<slug>.mp4, which is behind a Turnstile session (a request without a session returns 401).
Credentials: /opt/opus/videolabs/.env (loaded by the scripts; template .env.example). Never print, copy or commit secrets. The GitHub repo is public (the user is fine with that for the narration audio, scripts, captures and lab output, and does not mind throwaway passwords of destroyed labs appearing there), so no real credential may appear in any file. This skill's source is skills/videolabs_publish/ in the repo, linked into ~/.claude/skills by scripts/setup.sh.
What a lab needs (checked by the script)
out/<slug>.mp4(fast-start:moovbeforemdat; fix withffmpeg -i in.mp4 -c copy -movflags +faststart out.mp4),out/player/chapters.json(node engine/cli.mjs <slug> player),out/spot.jpg(poster; grab a diagram frame withffmpeg -ss 425 -i out/<slug>.mp4 -frames:v 1 -q:v 3 out/spot.jpg).- Optional
out/player/lab.json(fromnode engine/cli.mjs <slug> playerwhen the lab haslab/lab-meta.json,lab/*.sh,lab/captures/). If present,site/lab.jsshows the lab resources on the lab page. Re-runnode engine/cli.mjs <slug> playerfirst if captures orlab-meta.jsonchanged. labs/<slug>/lab.config.jsonwithtitle,descriptionandtags. The script adds or updates the entry insite/labs.jsonfrom it (and fills inminutesandlessons), so never edit the catalog by hand. For a new lab take title/description/tags from the lab's script if clear, otherwise ask the user. Never invent them silently.
Steps
- Plan.
cd /opt/opus/videolabs && scripts/publish-all.sh --dry-run. It prints onePLANline per lab (what will be uploaded or skipped, whether it haslab.json),ATTENTIONlines for labs it cannot publish (missing file, not fast-start, nolab.config.json) andNOTElines for catalog entries without a project. Exit code 2 means there is at least oneATTENTION. Fix each one (writelab.config.json, runnode engine/cli.mjs <slug> player, make the poster, ...) and repeat until there are none, or tell the user which lab is being left out and why. Show the user the plan in a few lines, including which videos will be uploaded and their size.- A lab with no recorded upload (
state/<slug>.sha256missing) is uploaded. If the user says R2 already holds exactly that file, use--adopt <slug>to record its hash instead of uploading.--force-uploadre-uploads everything; use it only when the user asks to replace a video (a re-upload may stay cached up to 24h for viewers).
- A lab with no recorded upload (
- Sync.
scripts/publish-all.sh(same flags). For every ready lab it first compresses the render toout/<slug>.web.mp4(scripts/compress-mp4.sh: mono AAC 64k + x264 CRF 28, about -65% size, ~5 min per 12 min of video, cached while the render is unchanged; the render itself is never modified), then uploads that web copy to R2 if changed, adds/updates the catalog entry fromlab.config.json, copieschapters.json,poster.jpg,lab.json(removed from the site folder when the lab has none), creates the pageindex.htmlfor a new lab (from the shared template, with title and description), records the video hash instate/<slug>.sha256and refreshesminutes/lessonsinsite/labs.json. Uploads happen here, before anything is pushed, so the page never goes live without its video. It is safe to run again. - Review and commit. Run
scripts/check-secrets.shfirst (it fails if a value from.envappears in any file, if a file assigns a value to an API key, token or password variable, or if a private key is present) and fix whatever it reports before going on. Show the usergit statusand a diff summary of the videolabs repo (new/changed labs,labs.json,state/, and any change to shared player code, see below). If the lab answers a GitHub video request (an issue labeledvideo-request), putCloses #Nin the commit message body so the push tomaincloses it. Commit everything, includinglabs/*/audio/when it changed (message likePublish labs: ceph-first-steps (new), kafka-in-a-nutshell (lab page), with the Co-Authored-By line from the session's attribution reminder) andgit push origin main(SSH remote is set up). If the working tree has changes to shared player code that were not part of this request, ask before including them. - Deploy. Wait for the GitHub Actions run (
curl -s https://api.github.com/repos/bigzaqui/videolabs/actions/runs?per_page=1, checkstatus/conclusion; poll about every 20 s up to ~3 min). If it fails, report it and do not retry blindly; a missing-secret error means the repository secretsCLOUDFLARE_API_TOKEN/CLOUDFLARE_ACCOUNT_IDneed attention. - Verify every lab in
site/labs.json(not only the new one):/returns 200 and lists them;/labs/<slug>/returns 200;/labs/<slug>/chapters.jsonreturns JSON (not the HTML fallback: the site answers 200 for unknown paths, so check the content type or the body);/labs/<slug>/lab.jsonreturns JSON for labs that have one;/media/<slug>/<slug>.mp4returns 401 without a session (proves the gate is up). Playback itself needs a browser session with the Turnstile check, so say that it was not played. Report the URLs:https://videolabs.pages.dev/labs/<slug>/.
To publish only one lab (rare), run scripts/publish-lab.sh <lab-dir> <slug> "<title>" "<description>" and update site/labs.json by hand, then continue with step 3.
Shared code
site/labs/kafka-in-a-nutshell/index.html is the page template copied for every new lab (title, heading and description come from site/labs.json at runtime), site/lab.js renders lab resources when a lab has lab.json, site/gate.js is the Turnstile check. Changing them affects every lab on the next push, so check them against an existing lab first. Existing labs keep their own copy of index.html; a template change reaches them only if their copy is updated too.