Skip to content
85 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Fast pages, required checks on the branch, self-hosted runners, honest incidents1//! What a self-hosted runner calls: registering with a registration token,
2//! then polling for work, saying how work ended and removing itself, with
3//! its own credential. Neither is a g1t access token, so these are served
4//! before anything reads one, and no other endpoint accepts either.
5//!
6//! | Route | Body |
7//! | --- | --- |
Merge branch 'claude/cool-newton-7nm8r9' (#36): real GitHub Actions on g1t, managed deploy workflows, g1t/deploy and the Sysbox runner 0.2.0. The merge keeps both sides' webhook events and API operations, says agent images need tar, guards the job engine's stop to Unix so the runner builds on Windows again, and keeps the moves tests' previews from going idle as the calendar passes their fixture's date.8//! | `POST /runners/register` | `token`, `name`, `labels`, `os`, `arch`, `version`, `ephemeral`, `group`, `replace`, `isolation` |
Fast pages, required checks on the branch, self-hosted runners, honest incidents9//! | `POST /runners/{id}/poll` | `version`, `running`, `wait_ms` |
10//! | `POST /runners/{id}/finished` | `id`, `exit_code`, `reason` |
11//! | `POST /runners/{id}/remove` | nothing |
12//!
13//! The last three send `Authorization: Bearer g1tr_…`. See
14//! `g1t_contracts::runners` for what each answers.
15
16use g1t_contracts::Outcome;
17use g1t_kit::wire;
18use serde_json::{Value, json};
19use worker::{Request, Response, Result};
20
21use crate::operations::Services;
22use crate::{fail, failure, json_body};
23
24/// What is passed through as given: an agent task's environment.
25const AS_GIVEN: &[&str] = &["env"];
26
27fn credential(request: &Request) -> Result<String> {
28 let header = request.headers().get("authorization")?.unwrap_or_default();
29 Ok(match header.split_once(' ') {
30 Some((scheme, token)) if scheme.eq_ignore_ascii_case("bearer") => token.trim().to_owned(),
31 _ => String::new(),
32 })
33}
34
35async fn answer(services: &Services, method: &str, args: &Value) -> Result<Response> {
36 let answered: Outcome<Value> = g1t_kit::call(&services.actions, method, args).await?;
37 match answered {
38 Outcome::Ok(value) => Response::from_json(&wire::snake_case_keeping(value, AS_GIVEN)),
39 Outcome::Fail(refused) => failure(&refused),
40 }
41}
42
43/// Serves `path` if it is one of the runner's routes.
44pub async fn handle(request: &mut Request, services: &Services, path: &str) -> Result<Option<Response>> {
45 let Some(rest) = path.strip_prefix("/runners/") else { return Ok(None) };
46 let body = json_body(request).await;
47 let body = if body.is_object() { body } else { json!({}) };
48 if rest == "register" {
49 let args = json!({
50 "token": body["token"].as_str().unwrap_or_default(),
51 "name": body["name"].as_str().unwrap_or_default(),
52 "labels": body["labels"].as_array().cloned().unwrap_or_default(),
53 "os": body["os"].as_str().unwrap_or_default(),
54 "arch": body["arch"].as_str().unwrap_or_default(),
55 "version": body["version"].as_str().unwrap_or_default(),
56 "ephemeral": body["ephemeral"].as_bool().unwrap_or(false),
57 "group": body["group"].as_str(),
58 "replace": body["replace"].as_bool().unwrap_or(false),
Merge branch 'claude/cool-newton-7nm8r9' (#36): real GitHub Actions on g1t, managed deploy workflows, g1t/deploy and the Sysbox runner 0.2.0. The merge keeps both sides' webhook events and API operations, says agent images need tar, guards the job engine's stop to Unix so the runner builds on Windows again, and keeps the moves tests' previews from going idle as the calendar passes their fixture's date.59 "isolation": body["isolation"].as_str(),
Fast pages, required checks on the branch, self-hosted runners, honest incidents60 });
61 return Ok(Some(answer(services, "runner_register", &args).await?));
62 }
63 let Some((runner, action)) = rest.split_once('/') else {
64 return Ok(Some(fail(g1t_contracts::FailureCode::NotFound, "No such endpoint.")?));
65 };
66 let auth = json!({ "runner": runner, "credential": credential(request)? });
67 let mut args = auth;
68 let method = match action {
69 "poll" => {
70 args["version"] = json!(body["version"].as_str().unwrap_or_default());
71 args["running"] = json!(body["running"].as_array().cloned().unwrap_or_default());
72 args["wait_ms"] = json!(body["wait_ms"].as_u64().unwrap_or(0));
73 "runner_poll"
74 }
75 "finished" => {
76 args["id"] = json!(body["id"].as_str().unwrap_or_default());
77 args["exit_code"] = json!(body["exit_code"].as_i64().unwrap_or(1));
78 args["reason"] = body["reason"].clone();
79 "runner_finished"
80 }
81 "remove" => "runner_remove_self",
82 _ => return Ok(Some(fail(g1t_contracts::FailureCode::NotFound, "No such endpoint.")?)),
83 };
84 Ok(Some(answer(services, method, &args).await?))
85}

This file's history is long; its oldest lines are credited to the oldest commit read.