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 incidents | 1 | //! 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 incidents | 9 | //! | `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 | ||
| 16 | use g1t_contracts::Outcome; | |
| 17 | use g1t_kit::wire; | |
| 18 | use serde_json::{Value, json}; | |
| 19 | use worker::{Request, Response, Result}; | |
| 20 | ||
| 21 | use crate::operations::Services; | |
| 22 | use crate::{fail, failure, json_body}; | |
| 23 | ||
| 24 | /// What is passed through as given: an agent task's environment. | |
| 25 | const AS_GIVEN: &[&str] = &["env"]; | |
| 26 | ||
| 27 | fn 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 | ||
| 35 | async 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. | |
| 44 | pub 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 incidents | 60 | }); |
| 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.