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
289 linesCodeBlameRaw
1import type { User } from "./identity";
2import type { RepoPath } from "./repos";
3import type { Result } from "./result";
4
5/**
6 * Self-hosted runners: a workspace's or a repository's own machines, which
7 * run its workflow jobs (and, when it says so, its agents' work) instead of
8 * g1t's sandboxes, at $0. Mirrors `crates/contracts/src/runners.rs`.
9 */
10
11export type RunnerStatus = "online" | "busy" | "offline";
12
13/** What a runner is doing now. */
14export type RunnerWork = {
15 kind: "workflow" | "agent";
16 id: string;
17 name: string;
18 repo: string | null;
19 /** The workflow run the job is in; for `agent` work, the agent run it is, when known. */
20 runId: string | null;
21 startedAt: string | null;
22};
23
24export type Runner = {
25 id: string;
26 name: string;
27 workspace: string;
28 /** `owner/name` for a repository's own runner; null for the workspace's. */
29 repo: string | null;
30 group: string | null;
31 labels: string[];
32 os: "linux" | "macos" | "windows";
33 arch: "x64" | "arm64";
34 version: string;
35 ephemeral: boolean;
36 status: RunnerStatus;
37 work: RunnerWork | null;
38 lastSeenAt: string | null;
39 createdAt: string;
40 createdBy: string | null;
41 /** How it keeps work from its machine, as it last said; null for a runner from before runners said. */
42 isolation?: RunnerIsolation | null;
43};
44
45/**
46 * `nested-container`: a container per job inside the runner's own sandboxed container (or VM).
47 * `vm`: a throwaway VM per job, native to the machine's OS (Windows Sandbox).
48 * `container`: a container per job on a Docker Engine shared with the machine.
49 * `host`: directly on the machine, unsandboxed.
50 */
51export type RunnerIsolation = "nested-container" | "vm" | "container" | "host";
52
53export type RunnerGroup = {
54 id: string;
55 name: string;
56 default: boolean;
57 /** Repository names; empty is every repository in the workspace. */
58 repositories: string[];
59 runners: number;
60 updatedAt: string;
61};
62
63/** Shown once. */
64export type RegistrationToken = {
65 token: string;
66 expiresAt: string;
67 workspace: string;
68 repo: string | null;
69 group: string | null;
70 url: string;
71};
72
73export type RunnerSettings = {
74 agentsOnSelfHosted: boolean;
75 agentLabels: string[];
76 forkPullRequests: boolean;
77 /** For a repository: these are its workspace's. */
78 inherited: boolean;
79};
80
81export type RunnersOwner = { repo: RepoPath } | { workspace: string };
82
83export type RunnerSettingsChange = {
84 agents_on_self_hosted?: boolean;
85 agent_labels?: string[];
86 fork_pull_requests?: boolean;
87 inherit?: boolean;
88};
89
90/** A workflow job running in one of g1t's sandboxes. Snake case, as `runner_activity` answers. */
91export type CloudJob = { id: string; name: string; run_id: string; repo: string; started_at: string | null };
92
93/** Agent work handed to one of the workspace's own runners, waiting for one or taken. */
94export type HandedOverTask = {
95 id: string;
96 /** The agent run it is; null for work handed over before runs were noted. */
97 run_id: string | null;
98 kind: string;
99 title: string;
100 repo: string;
101 status: "queued" | "in_progress";
102 runner_name: string | null;
103 created_at: string;
104 started_at: string | null;
105};
106
107/** What runs for a workspace now, in g1t's sandboxes and on its own runners (`runner_activity`). */
108export type RunnerActivity = {
109 /** At most `RUNNER_ACTIVITY_LIMIT`, newest first. */
110 cloud_jobs: CloudJob[];
111 cloud_jobs_queued: number;
112 handed_over: HandedOverTask[];
113 self_hosted_jobs_queued: number;
114};
115
116/** The most jobs and tasks `runner_activity` lists of each: `ACTIVITY_LIMIT` in the actions service. */
117export const RUNNER_ACTIVITY_LIMIT = 100;
118
119/** A job that has waited ten minutes or more with no matching runner online. */
120export type StuckJob = { id: string; name: string; runId: string; repo: string; labels: string; queuedAt: string };
121
122/** A self-hosted runner as staff see it. Mirrors `AdminRunner` in crates/contracts. */
123export type AdminRunner = {
124 id: string;
125 name: string;
126 /** `workspace/repo` for a repository's own runner. */
127 repo: string | null;
128 os: string;
129 version: string;
130 /** Running something now. */
131 busy: boolean;
132 lastSeenAt: string | null;
133 createdAt: string;
134 createdBy: string | null;
135};
136
137/** Staff-only: a workspace's runners, and removing them (sudo). */
138export interface RunnersAdminApi {
139 list(workspace: string): Promise<AdminRunner[]>;
140 /** Removes one runner, or with `id` null every one, and the workspace's registration tokens. How many went. */
141 remove(workspace: string, id: string | null, staff: string): Promise<Result<number>>;
142}
143
144export interface RunnersApi {
145 /** The viewer's workspaces' jobs stuck waiting for a self-hosted runner, for Mission control. */
146 stuck(viewer: User): Promise<StuckJob[]>;
147 /** What runs for the workspace now, in g1t's sandboxes and handed to its own runners. Owners. */
148 activity(actor: User, workspace: string): Promise<Result<RunnerActivity>>;
149 list(actor: User, owner: RunnersOwner): Promise<Result<Runner[]>>;
150 createToken(actor: User, owner: RunnersOwner, group?: string): Promise<Result<RegistrationToken>>;
151 remove(actor: User, owner: RunnersOwner, id: string): Promise<Result<boolean>>;
152 groups(actor: User, workspace: string): Promise<Result<RunnerGroup[]>>;
153 /** Without `id`, a new group. */
154 setGroup(actor: User, workspace: string, group: { id?: string; name?: string; repositories?: string[] }): Promise<Result<RunnerGroup>>;
155 deleteGroup(actor: User, workspace: string, id: string): Promise<Result<boolean>>;
156 settings(actor: User, owner: RunnersOwner): Promise<Result<RunnerSettings>>;
157 setSettings(actor: User, owner: RunnersOwner, change: RunnerSettingsChange): Promise<Result<RunnerSettings>>;
158 /** What the person's own desktop app says of its runner, in a workspace they own. */
159 reportDesktop(actor: User, report: DesktopReport): Promise<Result<boolean>>;
160 /** The workspace's desktops, as each app last said. Owners. */
161 desktops(actor: User, workspace: string): Promise<Result<DesktopRunner[]>>;
162}
163
164/**
165 * The desktop app keeps a runner on a person's computer while it is open.
166 * What it says of that runner (its state, why, and the fix) comes from one
167 * catalogue in the app (apps/desktop/src-tauri/src/runner/diagnosis.rs),
168 * and the site shows it word for word: `closed` is what to show once g1t
169 * is closed on that computer, which the app sends too.
170 */
171export type DesktopDiagnosis = {
172 code: string;
173 tone: "ok" | "working" | "off" | "warning" | "problem";
174 title: string;
175 cause: string | null;
176 fix: string | null;
177 /** A sandboxed desktop's kinds of job, each with how it is: Linux jobs can run while Windows jobs cannot. */
178 capabilities?: DesktopCapability[];
179 /** The kinds of job say all of this, each for itself: a page showing them shows no line of the whole runner's as well. */
180 in_capabilities?: boolean;
181 /** How its runner's updates stand. From newer apps; older ones leave it out. */
182 update?: DesktopUpdate | null;
183};
184
185/**
186 * How a desktop's runner updates stand, as its app says it. The app looks
187 * every hour, and installs a newer runner kind of job by kind of job, as
188 * each is idle.
189 */
190export type DesktopUpdate = {
191 /** When the app last heard from the runner's releases, RFC 3339. */
192 checked_at: string | null;
193 /** A newer version, downloaded and checked, waiting to be installed. */
194 ready: string | null;
195 /** What becomes of it, for a desktop with no kinds of job of its own: `0.3.2 ready, installs after this job`. */
196 note: string | null;
197};
198
199/** How one kind of job is, as its card's label says it. */
200export type DesktopCapabilityState = "busy" | "idle" | "starting" | "downloading" | "updating" | "waiting" | "stopped" | "unavailable" | "problem";
201
202/** What a kind of job is running. */
203export type DesktopJob = {
204 /** `workflow` or `agent`. */
205 kind: string;
206 name: string;
207 /** `acme/web`. */
208 repo: string;
209};
210
211/**
212 * One kind of job a sandboxed desktop runs (`linux`, `windows`, `macos`),
213 * as its app says it. Each starts and stops on its own: `enabled` is false
214 * for one the person stopped, which is not failing. The fields after
215 * `fix` come from newer apps; older ones leave them out.
216 */
217export type DesktopCapability = {
218 id: string;
219 /** `Linux jobs`. */
220 label: string;
221 tone: DesktopDiagnosis["tone"];
222 /** `Waiting for jobs`, `Not available`: its state in a few words. */
223 title: string;
224 cause: string | null;
225 fix: string | null;
226 state?: DesktopCapabilityState;
227 /** What is wrong, in a few words: `WSL isn't installed`. */
228 headline?: string | null;
229 /** What it is, in a line: `A Linux VM of its own, each job in a fresh container`. */
230 about?: string | null;
231 /** On in the person's settings; false when they stopped it. */
232 enabled?: boolean;
233 job?: DesktopJob | null;
234 /** Its runner's name on g1t: `build-box-linux`. */
235 runner?: string | null;
236 /** `nested-container` or `vm`. */
237 isolation?: string | null;
238 version?: string | null;
239 /** A newer version waiting for it: `0.3.2 ready, installs after this job`. */
240 update?: string | null;
241};
242
243/** How a desktop runs jobs. */
244export type DesktopBackend = "sandboxed" | "docker" | "host";
245
246export type DesktopReport = {
247 workspace: string;
248 machine: string;
249 backend: DesktopBackend;
250 version: string | null;
251 diagnosis: DesktopDiagnosis;
252 closed: DesktopDiagnosis;
253};
254
255/** One person's computer, as its desktop app last said. */
256export type DesktopRunner = {
257 workspace: string;
258 machine: string;
259 /** Whose computer: their username. */
260 person: string;
261 backend: DesktopBackend;
262 version: string | null;
263 diagnosis: DesktopDiagnosis;
264 closed: DesktopDiagnosis;
265 /** Heard from in the last ten minutes: g1t is open there. */
266 open: boolean;
267 reportedAt: string;
268};
269
270/** The label every desktop app's runner has. */
271export const DESKTOP_LABEL = "desktop";
272
273/**
274 * The runner's container image: `g1t-runner` and the Docker CLI, for
275 * running it in Docker or Kubernetes. Published with each release.
276 */
277export const RUNNER_IMAGE = "g1t.sh/flagon-io/g1t-runner";
278
279/** Where the runner binary is published: `g1t.sh/downloads/runner/<version>/<file>`. */
280export const RUNNER_DOWNLOADS = "https://g1t.sh/downloads/runner";
281
282/** The file for each platform, as published. */
283export const RUNNER_FILES: Record<"linux-x64" | "linux-arm64" | "macos-arm64" | "macos-x64" | "windows-x64", string> = {
284 "linux-x64": "g1t-runner-linux-x64",
285 "linux-arm64": "g1t-runner-linux-arm64",
286 "macos-arm64": "g1t-runner-macos-arm64",
287 "macos-x64": "g1t-runner-macos-x64",
288 "windows-x64": "g1t-runner-windows-x64.exe",
289};