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 | //! | --- | --- | --- | | |
| The desktop app runs jobs sandboxed on Windows, macOS and Linux. Its Sandboxed choice, the default, is now g1t-runner's sandbox launcher on every computer, where on Windows and macOS it said a sandbox isn't available on this computer yet and ran nothing. A few seconds after g1t opens, the app runs `g1t-runner sandbox up --json` with its own copy of the runner and its state in the app's data folder, its URL, the computer's name and the `desktop` label (the launcher adds `-linux` and `-windows` and the OS labels itself), and the registration token from the site the first time only: the launcher keeps the registration after that, across updates too, and a workspace's sandbox is remembered (sandbox-registration.json), so choosing Sandboxed again after another way of running jobs needs no new token. Linux jobs run in a Linux VM of the runner's own (WSL 2 on Windows, vfkit on macOS, Sysbox or QEMU with KVM on Linux), and on Windows 11 Pro and up Windows jobs run each in a Windows Sandbox. While it runs, the app asks `sandbox status --json` every eight seconds, on a thread of its own, for the tray, settings and the site. Quitting or Stop runs `sandbox down`; choosing another way of running jobs waits for the job and runs `down --drain`; moving to another workspace removes the sandbox (`down --delete`) and makes it again. A new runner release is put in place while nothing runs, and the sandbox then takes the release's image with `sandbox update`; every six hours, `update` also runs on an idle sandbox, which does nothing when its image is the newest. | 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 | ||
| 14 | use std::process::{Command, Stdio}; | |
| 15 | ||
| 16 | use serde::{Deserialize, Serialize}; | |
| 17 | ||
| 18 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | |
| 19 | #[serde(rename_all = "snake_case")] | |
| 20 | pub enum Backend { | |
| 21 | #[default] | |
| 22 | Sandboxed, | |
| 23 | Docker, | |
| 24 | Host, | |
| 25 | } | |
| 26 | ||
| 27 | impl 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)] | |
| 40 | pub enum Engine { | |
| The desktop app runs jobs sandboxed on Windows, macOS and Linux. Its Sandboxed choice, the default, is now g1t-runner's sandbox launcher on every computer, where on Windows and macOS it said a sandbox isn't available on this computer yet and ran nothing. A few seconds after g1t opens, the app runs `g1t-runner sandbox up --json` with its own copy of the runner and its state in the app's data folder, its URL, the computer's name and the `desktop` label (the launcher adds `-linux` and `-windows` and the OS labels itself), and the registration token from the site the first time only: the launcher keeps the registration after that, across updates too, and a workspace's sandbox is remembered (sandbox-registration.json), so choosing Sandboxed again after another way of running jobs needs no new token. Linux jobs run in a Linux VM of the runner's own (WSL 2 on Windows, vfkit on macOS, Sysbox or QEMU with KVM on Linux), and on Windows 11 Pro and up Windows jobs run each in a Windows Sandbox. While it runs, the app asks `sandbox status --json` every eight seconds, on a thread of its own, for the tray, settings and the site. Quitting or Stop runs `sandbox down`; choosing another way of running jobs waits for the job and runs `down --drain`; moving to another workspace removes the sandbox (`down --delete`) and makes it again. A new runner release is put in place while nothing runs, and the sandbox then takes the release's image with `sandbox update`; every six hours, `update` also runs on an idle sandbox, which does nothing when its image is the newest. | 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)] | |
| 51 | pub 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. | |
| 57 | pub 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. | |
| 70 | pub 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. | |
| 80 | pub fn engine(backend: Backend) -> Result<Engine, Missing> { | |
| 81 | match backend { | |
| The desktop app runs jobs sandboxed on Windows, macOS and Linux. Its Sandboxed choice, the default, is now g1t-runner's sandbox launcher on every computer, where on Windows and macOS it said a sandbox isn't available on this computer yet and ran nothing. A few seconds after g1t opens, the app runs `g1t-runner sandbox up --json` with its own copy of the runner and its state in the app's data folder, its URL, the computer's name and the `desktop` label (the launcher adds `-linux` and `-windows` and the OS labels itself), and the registration token from the site the first time only: the launcher keeps the registration after that, across updates too, and a workspace's sandbox is remembered (sandbox-registration.json), so choosing Sandboxed again after another way of running jobs needs no new token. Linux jobs run in a Linux VM of the runner's own (WSL 2 on Windows, vfkit on macOS, Sysbox or QEMU with KVM on Linux), and on Windows 11 Pro and up Windows jobs run each in a Windows Sandbox. While it runs, the app asks `sandbox status --json` every eight seconds, on a thread of its own, for the tray, settings and the site. Quitting or Stop runs `sandbox down`; choosing another way of running jobs waits for the job and runs `down --drain`; moving to another workspace removes the sandbox (`down --delete`) and makes it again. A new runner release is put in place while nothing runs, and the sandbox then takes the release's image with `sandbox update`; every six hours, `update` also runs on an idle sandbox, which does nothing when its image is the newest. | 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. | |
| 97 | pub 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)] | |
| 118 | mod 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] | |
| The desktop app runs jobs sandboxed on Windows, macOS and Linux. Its Sandboxed choice, the default, is now g1t-runner's sandbox launcher on every computer, where on Windows and macOS it said a sandbox isn't available on this computer yet and ran nothing. A few seconds after g1t opens, the app runs `g1t-runner sandbox up --json` with its own copy of the runner and its state in the app's data folder, its URL, the computer's name and the `desktop` label (the launcher adds `-linux` and `-windows` and the OS labels itself), and the registration token from the site the first time only: the launcher keeps the registration after that, across updates too, and a workspace's sandbox is remembered (sandbox-registration.json), so choosing Sandboxed again after another way of running jobs needs no new token. Linux jobs run in a Linux VM of the runner's own (WSL 2 on Windows, vfkit on macOS, Sysbox or QEMU with KVM on Linux), and on Windows 11 Pro and up Windows jobs run each in a Windows Sandbox. While it runs, the app asks `sandbox status --json` every eight seconds, on a thread of its own, for the tray, settings and the site. Quitting or Stop runs `sandbox down`; choosing another way of running jobs waits for the job and runs `down --drain`; moving to another workspace removes the sandbox (`down --delete`) and makes it again. A new runner release is put in place while nothing runs, and the sandbox then takes the release's image with `sandbox update`; every six hours, `update` also runs on an idle sandbox, which does nothing when its image is the newest. | 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 })); |
| The desktop app runs jobs sandboxed on Windows, macOS and Linux. Its Sandboxed choice, the default, is now g1t-runner's sandbox launcher on every computer, where on Windows and macOS it said a sandbox isn't available on this computer yet and ran nothing. A few seconds after g1t opens, the app runs `g1t-runner sandbox up --json` with its own copy of the runner and its state in the app's data folder, its URL, the computer's name and the `desktop` label (the launcher adds `-linux` and `-windows` and the OS labels itself), and the registration token from the site the first time only: the launcher keeps the registration after that, across updates too, and a workspace's sandbox is remembered (sandbox-registration.json), so choosing Sandboxed again after another way of running jobs needs no new token. Linux jobs run in a Linux VM of the runner's own (WSL 2 on Windows, vfkit on macOS, Sysbox or QEMU with KVM on Linux), and on Windows 11 Pro and up Windows jobs run each in a Windows Sandbox. While it runs, the app asks `sandbox status --json` every eight seconds, on a thread of its own, for the tray, settings and the site. Quitting or Stop runs `sandbox down`; choosing another way of running jobs waits for the job and runs `down --drain`; moving to another workspace removes the sandbox (`down --delete`) and makes it again. A new runner release is put in place while nothing runs, and the sandbox then takes the release's image with `sandbox update`; every six hours, `update` also runs on an idle sandbox, which does nothing when its image is the newest. | 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.