Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
131 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.

The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor1//! Client for the g1t Worker's internal endpoints, which own all
2//! authentication and authorization decisions.
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca3//!
The docs folder is gone, and what it held lives where people read it: how a self-hosted g1t runs and how to deploy g1t to Cloudflare are pages on docs.g1t.sh under Run g1t yourself, and speed, rate limits and operating g1t.sh are sections of CONTRIBUTING.md; code that cited a file in docs/ now points to the page or section that covers it, or says what it means itself, and applied migrations and the runner images are left as they were.4//! Neither endpoint exists yet. What they are to do:
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca5//!
6//! - `POST /_internal/ssh/user` with `{ fingerprint, used }` resolves the key
7//! through identity's `principal_for_ssh_key` (`used` once the client has
8//! proved it holds the private key, so only then is its last use
9//! recorded) and answers [`User`], or 404 for an unknown key.
10//! - `POST /_internal/ssh/access` with `{ fingerprint, owner, repo, service }`
11//! resolves the key again, so a key deleted mid-session stops working,
12//! and asks repos' `git_access` with that principal: a deploy key reaches
13//! its one repository, and pushes only when it was given write access.
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor14
15use anyhow::{Context, Result};
16use serde::{Deserialize, Serialize};
17
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca18/// Who a key signs in as.
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor19#[derive(Clone, Debug, Deserialize)]
20pub struct User {
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca21 /// `usr_…`, or for a deploy key its repository's workspace (`wsp_…`).
22 pub id: String,
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor23 pub username: String,
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca24 /// Set for a deploy key: the one repository it reaches, `owner/name`.
25 #[serde(default)]
26 pub repo: Option<String>,
27 /// The key's fingerprint, as it was looked up with.
28 #[serde(skip)]
29 pub fingerprint: String,
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor30}
31
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca32impl User {
33 /// The name a greeting uses: the person, or a deploy key's repository.
34 pub fn greeting_name(&self) -> &str {
35 self.repo.as_deref().unwrap_or(&self.username)
36 }
37}
38
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor39/// An Artifacts remote and a short-lived token scoped to one repo.
40#[derive(Debug, Deserialize)]
41pub struct Access {
42 pub remote: String,
43 pub token: String,
44}
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
47pub enum Service {
48 #[serde(rename = "git-upload-pack")]
49 UploadPack,
50 #[serde(rename = "git-receive-pack")]
51 ReceivePack,
52}
53
54impl Service {
55 pub fn as_str(self) -> &'static str {
56 match self {
57 Service::UploadPack => "git-upload-pack",
58 Service::ReceivePack => "git-receive-pack",
59 }
60 }
61}
62
63#[derive(Deserialize)]
64struct ErrorBody {
65 error: String,
66}
67
68pub struct Api {
69 base: String,
70 secret: String,
71 pub http: reqwest::Client,
72}
73
74impl Api {
75 pub fn new(base: String, secret: String) -> Self {
76 Self {
77 base,
78 secret,
79 http: reqwest::Client::new(),
80 }
81 }
82
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca83 /// Who the key with this SHA-256 fingerprint signs in as: the person
84 /// who registered it, or a repository's deploy key. `used` once the
85 /// client has proved it holds the private key.
86 pub async fn user_for_key(&self, fingerprint: &str, used: bool) -> Result<Option<User>> {
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor87 let response = self
88 .http
89 .post(format!("{}/_internal/ssh/user", self.base))
90 .bearer_auth(&self.secret)
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca91 .json(&serde_json::json!({ "fingerprint": fingerprint, "used": used }))
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor92 .send()
93 .await
94 .context("key lookup failed")?;
95 if response.status() == reqwest::StatusCode::NOT_FOUND {
96 return Ok(None);
97 }
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca98 let mut user: User = response.error_for_status()?.json().await?;
99 user.fingerprint = fingerprint.to_owned();
100 Ok(Some(user))
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor101 }
102
103 /// `Ok(Err(message))` is a refusal to show the user.
104 pub async fn access(
105 &self,
106 user: &User,
107 owner: &str,
108 repo: &str,
109 service: Service,
110 ) -> Result<Result<Access, String>> {
111 let response = self
112 .http
113 .post(format!("{}/_internal/ssh/access", self.base))
114 .bearer_auth(&self.secret)
115 .json(&serde_json::json!({
116 "user_id": user.id,
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca117 "fingerprint": user.fingerprint,
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor118 "owner": owner,
119 "repo": repo,
120 "service": service,
121 }))
122 .send()
123 .await
124 .context("access check failed")?;
125 if response.status().is_client_error() {
126 let body: ErrorBody = response.json().await?;
127 return Ok(Err(body.error));
128 }
129 Ok(Ok(response.error_for_status()?.json().await?))
130 }
131}

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