Skip to content

video + gameserver: finish both plugins and mark them available - #164

Merged
ralyodio merged 3 commits into
masterfrom
feat/video-gameserver-finish
Nov 9, 2026
Merged

ralyodio merged 3 commits into
masterfrom
feat/video-gameserver-finish

Conversation

@ralyodio

@ralyodio ralyodio commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator

Finishes the two partial plugins. Hardware needs are now listed in the manifests as requirements ([module.requirements] hardware, which the plugins page shows as "requires: ..."). They no longer count as reasons for partial.

video

  • Runner pinned by digest. RUNNER_PULL_REF = ghcr.io/profullstack/c0mpute-video-runner@sha256:2ba52bed… (the image the current master build published). The node runs it under a local name, c0mpute/video-runner:stable. A unit test keeps module.toml [module.dispatch] and RUNNER_DIGEST equal.
  • Workflow. Now also tags :stable, adds the OCI source label (links the package to this repo), writes the digest to pin into the job summary, and checks an anonymous pull (warns until it works).
  • Package visibility needs one manual click. GHCR creates org packages private, and there is no API to change that. An anonymous pull of c0mpute-video-runner returns UNAUTHORIZED today. An org admin needs to go to github.com/orgs/profullstack/packages → c0mpute-video-runner → Package settings → Change visibility → Public. Until then, c0mpute video setup builds the image from source.
  • video setup moved into core (video::setup). It pulls the pinned digest and tags it; if the pull fails, it clones and builds from source (C0MPUTE_VIDEO_SOURCE_REPO overrides the repo). install.sh does the same.
  • C0MPUTE_DOCKER overrides the container CLI, for podman or a test double.
  • speech_gateway::router() is exposed so POST /v1/videos can be tested end to end.
  • Manifest: status = "available", builtin = true, requires "NVIDIA GPU + docker with the NVIDIA container runtime".

gameserver (spec v2)

  • Secrets never go in the offer. The offer carries the buyer's fresh X25519 key. After the accept, the winner announces its own key and the buyer seals the Valheim password and any restore key to it. This is the new handoff.rs (X25519 → HKDF → AES-256-GCM, gossip-signed sources).
  • Encrypted backups. Every backup_mins (default 30) and at stop or expiry, the worker encrypts the world to the buyer's long-term X25519 key (backup.key, mode 0600) in 1 MiB AES-GCM chunks. Worlds published for a restore use a fresh key, handed only to the winning worker. Manifest is v2, and unencrypted manifests are refused.
  • Signed status records on c0mpute/gameserver/v1: container running, latest backup.
  • c0mpute gameserver watch <id>. Probes the game port: Minecraft status ping, Valheim Steam A2S, and for Factorio a UDP reachability check plus a fresh "running" status record. Every probe is logged (gameserver health <id>). It mirrors each backup to the buyer's machine. If the server fails checks for --fail-after-secs (default 300), it starts the same world on another worker from the newest backup, excludes the failed worker, and links the two rentals.
  • Hard expiry that does not need the worker online. The container runs under timeout <reservation> with --init --rm and --stop-timeout. At boot and on every sweep, the worker kills any labelled container that is past its expiry, then backs up from the volume and removes it.
  • A Runtime trait (Docker in production) lets the loopback e2e tests run without docker. Entrypoints come from the image configs at the pinned digests.
  • Manifest: status = "available", requires "opt-in worker role: docker + a public IP with the port range open".

Tests

cargo test --workspace --offline in --network none docker: 682 passed, 0 failed, 1 ignored (the ignored one was already there). New tests:

  • video_e2e:
  • gameserver_e2e:
    • Valheim: the password reaches only the winner, and a bystander sees neither it nor readable chunks. The stop downloads and decrypts the world.
    • Minecraft: watch mirrors A's backup; A's server dies; the watch fails over to B, which restores A's world and serves it.
    • An expiry missed while the worker was offline is enforced at the next sweep.
  • Unit tests: spec v1 refused, argv (timeout/--init/--rm), probe codecs against live loopback responders, health tracker, chunk encryption, backup key derivation.
  • Web: bun run lint and bun test pass (19).

Not verified with real docker or a GPU here: the game images under timeout and the real runner were not run on dev2.

🤖 Generated with Claude Code

ralyodio and others added 2 commits November 9, 2026 04:21
video: pin the published runner by digest (RUNNER_PULL_REF), run it under a
local name, tag :stable in the workflow, label the package with its source,
and check anonymous pulls in CI. `video setup` (now in core) pulls the pinned
digest or builds from source. C0MPUTE_DOCKER overrides the container CLI.
Speech gateway exposes router() so POST /v1/videos is tested end to end on
loopback with a fake runner that writes a real MP4.

gameserver (spec v2): secrets never ride in the offer (sealed handoff to the
winning worker); backups every 30 min and at stop, encrypted to the buyer's
X25519 key; signed status records on c0mpute/gameserver/v1; `gameserver watch`
probes the port (Minecraft status, Valheim A2S, Factorio UDP + status),
mirrors backups and fails over to another worker from the latest one; hard
expiry via timeout + --init + --rm plus a boot/sweep watchdog. Runtime trait
lets the loopback e2e run without docker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… video e2e

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown

ThreatCrush Security Scan

12 finding(s)

HIGH/CRITICAL: 1 | MEDIUM: 8 | LOW: 3

Severity Rule Location
HIGH js-ssrf-outbound-request apps/web/public/sw.js:24
MEDIUM js-unescaped-html-sink apps/web/src/app/blog/[slug]/page.tsx:53
MEDIUM js-unescaped-html-sink apps/web/src/app/layout.tsx:80
MEDIUM sh-remote-script-execution scripts/dev-setup.sh:25
MEDIUM sh-eval-expansion scripts/dev-setup.sh:35
MEDIUM sh-remote-script-execution scripts/install.sh:173
MEDIUM sh-remote-script-execution scripts/install.sh:198
MEDIUM sh-remote-script-execution scripts/install.sh:316
MEDIUM sh-remote-script-execution scripts/install.sh:354
LOW py-ssrf-outbound-request examples/business-strategist-agent/chat.py:37
LOW py-ssrf-outbound-request examples/business-strategist-agent/train.py:54
LOW secret-generic-credential node/crates/c0mpute-core/src/webhook.rs:1165

Snippets are redacted; ThreatCrush never prints matched credential material.

…nner

CI showed a failover worker that shared no connection with the buyer could
not fetch the restore world (Failed to dial the requested peer). Both sides
now put their addresses in the handoff and dial each other, so either side
can be the one behind NAT. Also fixes a unit test slicing a short last chunk.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ralyodio
ralyodio merged commit 60d6645 into master Nov 9, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant