glove/scripts/remote-workerd-validate.sh
trvon afecee2c90
feat(remote): add construction-only backend
Co-authored-by: trvon <git@trevon.dev>
Signed-off-by: trvon <git@trevon.dev>
2026-08-07 22:27:35 -04:00

208 lines
7.7 KiB
Bash
Executable file

#!/bin/sh
set -eu
if [ "${GLOVE_REMOTE_WORKERD_VALIDATE:-}" != "1" ]; then
echo "refusing: set GLOVE_REMOTE_WORKERD_VALIDATE=1 for this validation-only run" >&2
exit 2
fi
if [ "$#" -ne 2 ]; then
echo "usage: GLOVE_REMOTE_WORKERD_VALIDATE=1 $0 /absolute/glove-remote-exec /absolute/schema-2-config" >&2
exit 2
fi
executor=$1
config=$2
case "$executor:$config" in
/*:/*) ;;
*) echo "executor and config must be absolute paths" >&2; exit 2 ;;
esac
if [ ! -f "$executor" ] || [ -L "$executor" ] || [ ! -x "$executor" ]; then
echo "executor must be a preverified regular executable, not a symlink" >&2
exit 2
fi
if [ ! -f "$config" ] || [ -L "$config" ]; then
echo "config must be a preverified regular file, not a symlink" >&2
exit 2
fi
for required in \
GLOVE_REMOTE_EXECUTOR_SHA256 \
GLOVE_REMOTE_IMAGE \
GLOVE_REMOTE_WORKERD_SHA256 \
GLOVE_REMOTE_DESCRIPTOR_SHA256
do
eval "value=\${$required:-}"
if [ -z "$value" ]; then
echo "refusing: $required must be supplied from preverified operator input" >&2
exit 2
fi
done
# Python is used only from the local standard library. This script never
# installs, fetches, logs in, mounts host paths, or supplies Docker options.
exec /usr/bin/env -i \
GLOVE_REMOTE_EXECUTOR_SHA256="$GLOVE_REMOTE_EXECUTOR_SHA256" \
GLOVE_REMOTE_IMAGE="$GLOVE_REMOTE_IMAGE" \
GLOVE_REMOTE_WORKERD_SHA256="$GLOVE_REMOTE_WORKERD_SHA256" \
GLOVE_REMOTE_DESCRIPTOR_SHA256="$GLOVE_REMOTE_DESCRIPTOR_SHA256" \
/usr/bin/python3 - "$executor" "$config" <<'PY'
import hashlib
import json
import os
import re
import secrets
import struct
import subprocess
import sys
import time
from pathlib import Path
executor = Path(sys.argv[1])
config_path = Path(sys.argv[2])
expected_keys = [
"schema_version",
"executor_digest",
"container_image",
"container_image_digest",
"workerd_digest",
"descriptor_digest",
"staging_root",
"max_sessions",
"max_ttl_ms",
"docker_executable",
"docker_executable_digest",
]
raw = config_path.read_text(encoding="ascii")
if not raw.endswith("\n") or "\r" in raw or "\x00" in raw:
raise SystemExit("invalid canonical executor config")
lines = raw.splitlines()
if len(lines) != len(expected_keys):
raise SystemExit("invalid canonical executor config field count")
config = {}
for key, line in zip(expected_keys, lines):
prefix = key + "="
if not line.startswith(prefix) or not line[len(prefix):]:
raise SystemExit("invalid canonical executor config ordering")
config[key] = line[len(prefix):]
sha = lambda path: "sha256:" + hashlib.sha256(Path(path).read_bytes()).hexdigest()
digest_re = re.compile(r"sha256:[0-9a-f]{64}\Z")
image_re = re.compile(r"[a-z0-9][a-z0-9._-]*(?:/[a-z0-9][a-z0-9._-]*)+@sha256:[0-9a-f]{64}\Z")
if config["schema_version"] != "2":
raise SystemExit("validation requires schema_version=2")
if config["executor_digest"] != os.environ["GLOVE_REMOTE_EXECUTOR_SHA256"]:
raise SystemExit("operator executor digest does not match config")
if sha(executor) != config["executor_digest"]:
raise SystemExit("executor binary digest mismatch")
if config["container_image"] != os.environ["GLOVE_REMOTE_IMAGE"]:
raise SystemExit("operator image identity does not match config")
if not image_re.fullmatch(config["container_image"]):
raise SystemExit("image must be a canonical digest-qualified name")
if not config["container_image"].endswith("@" + config["container_image_digest"]):
raise SystemExit("image digest suffix mismatch")
if config["workerd_digest"] != os.environ["GLOVE_REMOTE_WORKERD_SHA256"]:
raise SystemExit("operator workerd digest does not match config")
if config["descriptor_digest"] != os.environ["GLOVE_REMOTE_DESCRIPTOR_SHA256"]:
raise SystemExit("operator descriptor digest does not match config")
if not digest_re.fullmatch(config["workerd_digest"]) or not digest_re.fullmatch(config["descriptor_digest"]):
raise SystemExit("invalid fixed descriptor identity")
if config["docker_executable"] != "/usr/bin/docker":
raise SystemExit("validation executor requires /usr/bin/docker")
if sha("/usr/bin/docker") != config["docker_executable_digest"]:
raise SystemExit("Docker executable digest mismatch")
# `image inspect` is local and read-only. `run --pull never` is also hardcoded
# inside the checked executor, so a missing image fails instead of fetching.
subprocess.run(
["/usr/bin/docker", "image", "inspect", config["container_image"]],
env={}, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL, check=True, timeout=10,
)
process = subprocess.Popen(
[str(executor), "--local-validation-stdio", str(config_path)],
env={}, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=None,
)
assert process.stdin is not None and process.stdout is not None
session_id = "local-workerd-validation"
epoch = secrets.token_hex(16)
descriptor = config["descriptor_digest"]
ttl = min(int(config["max_ttl_ms"]), 60_000)
request_number = 0
def canonical(method, payload):
value = (
"glove-remote-validation-v1\n"
f"method={method}\n"
f"session_id={payload['session_id']}\n"
f"session_epoch={payload['session_epoch']}\n"
f"descriptor_digest={payload['descriptor_digest']}\n"
f"idempotency_key={payload['idempotency_key']}"
)
if method == "remote_read":
value += f"\ncursor={payload['cursor']}\nmax_bytes={payload['max_bytes']}"
return value + "\n"
def call(method, key, **specific):
global request_number
request_number += 1
payload = {
"schema_version": 1,
"session_id": session_id,
"session_epoch": epoch,
"descriptor_digest": descriptor,
"idempotency_key": key,
"payload_digest": "sha256:" + "0" * 64,
**specific,
}
payload["payload_digest"] = "sha256:" + hashlib.sha256(
canonical(method, payload).encode("ascii")
).hexdigest()
request = json.dumps({
"jsonrpc": "2.0",
"id": f"local-{request_number}",
"method": method,
"deadline_remaining_ms": ttl,
"payload": payload,
}, separators=(",", ":")).encode("utf-8")
process.stdin.write(struct.pack(">I", len(request)) + request)
process.stdin.flush()
header = process.stdout.read(4)
if len(header) != 4:
raise RuntimeError("executor closed before response")
size = struct.unpack(">I", header)[0]
if size == 0 or size > 1024 * 1024:
raise RuntimeError("invalid executor response frame")
response = json.loads(process.stdout.read(size))
if response.get("error"):
raise RuntimeError(response["error"]["code"] + ": " + response["error"]["message"])
return response["result"]
try:
print(json.dumps(call("remote_prepare", "prepare"), sort_keys=True))
print(json.dumps(call("remote_start", "start"), sort_keys=True))
cursor = 0
for attempt in range(600):
chunk = call("remote_read", f"read-{attempt}", cursor=cursor, max_bytes=16384)
if chunk["bytes"]:
sys.stdout.write(chunk["bytes"])
sys.stdout.flush()
cursor = chunk["next_cursor"]
terminal = call("remote_wait", f"wait-{attempt}")
if terminal["state"] != "running":
print(json.dumps(terminal, sort_keys=True))
break
time.sleep(0.05)
else:
raise RuntimeError("validator did not terminate within the bounded polling window")
print(json.dumps(call("remote_cleanup", "cleanup"), sort_keys=True))
finally:
process.stdin.close()
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
process.kill()
process.wait()
if process.returncode != 0:
raise SystemExit(process.returncode)
PY