Skip to content
140 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.

Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.1//! How the runner runs on this computer, which is the person's choice in
2//! settings, and what each choice needs before it can start.
3//!
4//! | Backend | How | What a job can reach |
5//! | --- | --- | --- |
Merge the desktop app's sandbox: its default runner now starts g1t-runner's sandbox on Windows, macOS and Linux, and says per kind of job (Linux, Windows) what it is doing or why it cannot and how to fix it.6//! | Sandboxed (the default) | `g1t-runner sandbox` (launcher.rs): Linux jobs in a Linux VM of the runner's own (WSL 2 on Windows, vfkit on macOS, Sysbox or QEMU with KVM on Linux), each a container inside it, `nested-container`; on Windows 11 Pro and up, Windows jobs each in a Windows Sandbox, `vm`. What the computer lacks for it, the launcher says, and the app shows. | Its own container, or its own Windows Sandbox. Not this computer. |
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.7//! | Docker | `g1t-runner` on this computer, driving this computer's Docker (native.rs): `container` isolation. | Its own container; but the runner controls this computer's Docker, which on Docker Desktop is a VM with the person's files shared into it. Not a sandbox. |
8//! | On this computer | `g1t-runner --no-docker` (native.rs): `host` isolation. | Everything the person's account can. |
9//!
10//! The two that are not sandboxes are only ever chosen; nothing falls back
11//! to them. When the chosen one cannot start, the app says why and runs
12//! nothing.
13
14use std::process::{Command, Stdio};
15
16use serde::{Deserialize, Serialize};
17
18#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(rename_all = "snake_case")]
20pub enum Backend {
21 #[default]
22 Sandboxed,
23 Docker,
24 Host,
25}
26
27impl Backend {
28 /// How it is said in the tray and the log.
29 pub fn label(self) -> &'static str {
30 match self {
31 Backend::Sandboxed => "sandboxed",
32 Backend::Docker => "in Docker",
33 Backend::Host => "on this computer",
34 }
35 }
36}
37
38/// What starts the runner for a backend, here.
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub enum Engine {
Merge the desktop app's sandbox: its default runner now starts g1t-runner's sandbox on Windows, macOS and Linux, and says per kind of job (Linux, Windows) what it is doing or why it cannot and how to fix it.41 /// `g1t-runner sandbox`, which makes and owns the sandbox, and says
42 /// itself what this computer lacks for one.
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.43 Launcher,
44 /// `g1t-runner` itself, as a child of the app; `docker` is whether its
45 /// jobs run in this computer's Docker or directly on it.
46 Native { docker: bool },
47}
48
49/// Why a backend cannot start here (diagnosis.rs says it).
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub enum Missing {
52 /// Docker is not installed, or not running.
53 Docker,
54}
55
56/// A program run without a console window flashing up on Windows.
57pub fn quiet(program: impl AsRef<std::ffi::OsStr>) -> Command {
58 let mut command = Command::new(program);
59 #[cfg(windows)]
60 {
61 use std::os::windows::process::CommandExt;
62 const CREATE_NO_WINDOW: u32 = 0x0800_0000;
63 command.creation_flags(CREATE_NO_WINDOW);
64 }
65 command.stdin(Stdio::null());
66 command
67}
68
69/// Whether this computer's Docker answers.
70pub fn docker_ready() -> bool {
71 quiet("docker")
72 .args(["version", "--format", "{{.Server.Version}}"])
73 .stdout(Stdio::null())
74 .stderr(Stdio::null())
75 .status()
76 .is_ok_and(|status| status.success())
77}
78
79/// What a backend needs, checked now: how to start it, or why it cannot.
80pub fn engine(backend: Backend) -> Result<Engine, Missing> {
81 match backend {
Merge the desktop app's sandbox: its default runner now starts g1t-runner's sandbox on Windows, macOS and Linux, and says per kind of job (Linux, Windows) what it is doing or why it cannot and how to fix it.82 Backend::Sandboxed => Ok(Engine::Launcher),
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.83 Backend::Docker => {
84 if docker_ready() {
85 Ok(Engine::Native { docker: true })
86 } else {
87 Err(Missing::Docker)
88 }
89 }
90 Backend::Host => Ok(Engine::Native { docker: false }),
91 }
92}
93
94/// This computer's name, as the runner is called on g1t: what `g1t-runner`
95/// would choose itself, given here so a runner in a container, whose own
96/// host name is the container's, has the computer's.
97pub fn machine_name() -> String {
98 let given = ["COMPUTERNAME", "HOSTNAME"]
99 .iter()
100 .find_map(|name| std::env::var(name).ok().map(|v| v.trim().to_owned()).filter(|v| !v.is_empty()))
101 .or_else(|| {
102 quiet("hostname")
103 .output()
104 .ok()
105 .map(|out| String::from_utf8_lossy(&out.stdout).trim().to_owned())
106 .filter(|name| !name.is_empty())
107 })
108 .unwrap_or_else(|| "desktop".into());
109 let name: String = given
110 .chars()
111 .map(|c| if c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | '.') { c } else { '-' })
112 .take(64)
113 .collect();
114 if name.is_empty() { "desktop".into() } else { name }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn the_sandbox_is_the_default_and_names_read() {
123 assert_eq!(Backend::default(), Backend::Sandboxed);
124 assert_eq!(serde_json::to_string(&Backend::Host).unwrap(), r#""host""#);
125 assert_eq!(serde_json::from_str::<Backend>(r#""docker""#).unwrap(), Backend::Docker);
126 }
127
128 #[test]
Merge the desktop app's sandbox: its default runner now starts g1t-runner's sandbox on Windows, macOS and Linux, and says per kind of job (Linux, Windows) what it is doing or why it cannot and how to fix it.129 fn on_this_computer_needs_nothing_more_and_the_sandbox_is_the_launchers() {
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.130 assert_eq!(engine(Backend::Host), Ok(Engine::Native { docker: false }));
Merge the desktop app's sandbox: its default runner now starts g1t-runner's sandbox on Windows, macOS and Linux, and says per kind of job (Linux, Windows) what it is doing or why it cannot and how to fix it.131 assert_eq!(engine(Backend::Sandboxed), Ok(Engine::Launcher));
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.132 }
133
134 #[test]
135 fn a_machine_name_is_one_g1t_takes() {
136 let name = machine_name();
137 assert!(!name.is_empty() && name.len() <= 64);
138 assert!(name.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | '.')), "{name}");
139 }
140}

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