Skip to content
186 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 `g1t-runner register` keeps: where g1t is, who this runner is, its
2//! credential, and how it runs work. One JSON file in the runner's folder,
3//! readable by its owner only.
4
5use std::path::{Path, PathBuf};
6
7use anyhow::{Context, Result};
8use serde::{Deserialize, Serialize};
9
10#[derive(Clone, Debug, Serialize, Deserialize)]
11pub struct Config {
12 /// The site, as given to `--url`: `https://g1t.sh`.
13 pub url: String,
14 /// The API the runner calls: `https://api.g1t.sh`.
15 pub api: String,
16 /// The runner's id and name.
17 pub runner: String,
18 pub name: String,
19 /// Its credential (`g1tr_…`). Rotated by g1t every day.
20 pub credential: String,
21 pub workspace: String,
22 #[serde(default)]
23 pub repo: Option<String>,
24 #[serde(default)]
25 pub group: Option<String>,
26 pub labels: Vec<String>,
27 /// Runs one job, then removes itself.
28 #[serde(default)]
29 pub ephemeral: bool,
30 /// Where jobs keep their files when they run on this machine directly.
31 pub work_dir: PathBuf,
32 /// Runs work in Docker containers (the default), or directly.
33 #[serde(default = "yes")]
34 pub docker: bool,
35 /// The image workflow jobs run in when they name no `container:`.
36 #[serde(default)]
37 pub image: Option<String>,
38 /// The image agent work runs in: git, Node and the agent's CLI.
39 #[serde(default)]
40 pub agent_image: Option<String>,
41 /// A Linux build of `g1t-runner` to run inside containers, instead of
42 /// this binary (on Linux) or the release's (elsewhere).
43 #[serde(default)]
44 pub harness: Option<PathBuf>,
45 /// Checks for new versions and updates itself.
46 #[serde(default = "yes")]
47 pub auto_update: bool,
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.48 /// Each job container's memory limit (`--memory`): `8g`. Three
49 /// quarters of the machine's (or the runner container's) memory if
50 /// left out.
51 #[serde(default)]
52 pub memory: Option<String>,
53 /// Each job container's CPUs (`--cpus`): `2`, `1.5`. Every CPU if left out.
54 #[serde(default)]
55 pub cpus: Option<String>,
56 /// The most processes in a job container (`--pids-limit`). 4096 if left out.
57 #[serde(default)]
58 pub pids_limit: Option<u32>,
59 /// With nested-container isolation, a job that uses Docker gets an
60 /// engine of its own. Off: jobs have no Docker.
61 #[serde(default = "yes")]
62 pub job_docker: bool,
63 /// The image a job's own engine runs: `docker:28-dind` if left out.
64 #[serde(default)]
65 pub job_docker_image: Option<String>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents66}
67
68fn yes() -> bool {
69 true
70}
71
72/// The runner's folder: `--dir`, `G1T_RUNNER_DIR`, else `.g1t-runner` in
73/// the home folder.
74pub fn folder(given: Option<&str>) -> PathBuf {
75 if let Some(dir) = given.filter(|d| !d.is_empty()) {
76 return PathBuf::from(dir);
77 }
78 if let Some(dir) = std::env::var_os("G1T_RUNNER_DIR").filter(|d| !d.is_empty()) {
79 return PathBuf::from(dir);
80 }
81 let home = std::env::var_os(if cfg!(windows) { "USERPROFILE" } else { "HOME" })
82 .map(PathBuf::from)
83 .unwrap_or_else(|| PathBuf::from("."));
84 home.join(".g1t-runner")
85}
86
87pub fn path(folder: &Path) -> PathBuf {
88 folder.join("config.json")
89}
90
91pub fn load(folder: &Path) -> Result<Config> {
92 let file = path(folder);
93 let text = std::fs::read_to_string(&file)
94 .with_context(|| format!("this runner is not registered ({} not found): run `g1t-runner register` first", file.display()))?;
95 serde_json::from_str(&text).with_context(|| format!("{} does not read", file.display()))
96}
97
98/// Writes the configuration so only its owner can read it, through a
99/// temporary file, so a crash never leaves half a credential.
100pub fn save(folder: &Path, config: &Config) -> Result<()> {
101 std::fs::create_dir_all(folder).with_context(|| format!("could not make {}", folder.display()))?;
102 let file = path(folder);
103 let temp = folder.join("config.json.new");
104 std::fs::write(&temp, serde_json::to_string_pretty(config)?)?;
105 #[cfg(unix)]
106 {
107 use std::os::unix::fs::PermissionsExt;
108 std::fs::set_permissions(&temp, std::fs::Permissions::from_mode(0o600))?;
109 }
110 std::fs::rename(&temp, &file).with_context(|| format!("could not write {}", file.display()))?;
111 Ok(())
112}
113
114pub fn forget(folder: &Path) {
115 let _ = std::fs::remove_file(path(folder));
116}
117
118/// The API for a site: `https://g1t.sh` calls `https://api.g1t.sh`.
119pub fn api_for(url: &str) -> String {
120 let url = url.trim_end_matches('/');
121 match url.split_once("://") {
122 Some((scheme, host)) if !host.starts_with("api.") && !host.starts_with("localhost") && !host.starts_with("127.") => {
123 format!("{scheme}://api.{host}")
124 }
125 _ => url.to_owned(),
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn the_api_is_found_from_the_site() {
135 assert_eq!(api_for("https://g1t.sh"), "https://api.g1t.sh");
136 assert_eq!(api_for("https://g1t.sh/"), "https://api.g1t.sh");
137 assert_eq!(api_for("https://api.g1t.sh"), "https://api.g1t.sh");
138 assert_eq!(api_for("https://git.example.com"), "https://api.git.example.com");
139 assert_eq!(api_for("http://localhost:8787"), "http://localhost:8787");
140 }
141
142 #[test]
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.143 fn an_older_configuration_still_reads() {
144 let old = r#"{"url":"https://g1t.sh","api":"https://api.g1t.sh","runner":"rnr_1","name":"a","credential":"g1tr_x",
145 "workspace":"acme","labels":["self-hosted"],"work_dir":"/tmp/w"}"#;
146 let config: Config = serde_json::from_str(old).unwrap();
147 assert!(config.docker && config.job_docker && config.auto_update);
148 assert_eq!(config.memory, None);
149 }
150
151 #[test]
Fast pages, required checks on the branch, self-hosted runners, honest incidents152 fn a_configuration_survives_a_round_trip() {
153 let dir = std::env::temp_dir().join(format!("g1t-runner-config-{}", std::process::id()));
154 let config = Config {
155 url: "https://g1t.sh".into(),
156 api: "https://api.g1t.sh".into(),
157 runner: "rnr_1".into(),
158 name: "build-01".into(),
159 credential: "g1tr_x".into(),
160 workspace: "acme".into(),
161 repo: None,
162 group: Some("Default".into()),
163 labels: vec!["self-hosted".into(), "linux".into()],
164 ephemeral: false,
165 work_dir: dir.join("work"),
166 docker: true,
167 image: None,
168 agent_image: None,
169 harness: None,
170 auto_update: true,
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.171 memory: Some("4g".into()),
172 cpus: None,
173 pids_limit: None,
174 job_docker: true,
175 job_docker_image: None,
Fast pages, required checks on the branch, self-hosted runners, honest incidents176 };
177 save(&dir, &config).unwrap();
178 let back = load(&dir).unwrap();
179 assert_eq!(back.runner, "rnr_1");
180 assert_eq!(back.labels, config.labels);
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.181 assert_eq!(back.memory.as_deref(), Some("4g"));
Fast pages, required checks on the branch, self-hosted runners, honest incidents182 forget(&dir);
183 assert!(load(&dir).is_err());
184 let _ = std::fs::remove_dir_all(&dir);
185 }
186}

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