git_policy_cli/landing/
service_route.rs1use crate::repo_read_layout::read_layout_in;
17use crate::service_instance::{SOCKET_DIRECTORY, instance_of, socket_for};
18use crate::service_provision::{ProvisionHost, RETIREMENT_WAIT, SERVICE_PROGRAM};
19use crate::service_server::Bind;
20use crate::systemd_dbus::session_bus_path;
21use crate::transaction_git::{GitContext, GitRequest, run_git};
22use std::path::{Path, PathBuf};
23
24static OWN_EXECUTABLE: std::sync::OnceLock<PathBuf> = std::sync::OnceLock::new();
25
26pub fn remember_own_executable(path: &Path) {
30 let _first = OWN_EXECUTABLE.set(path.to_path_buf());
31}
32
33pub fn remembered_own_executable() -> Option<&'static Path> {
35 return OWN_EXECUTABLE.get().map(PathBuf::as_path);
36}
37
38#[derive(Clone, Copy, Debug)]
40pub struct RouteInputs<'a> {
41 pub runtime_dir: Option<&'a Path>,
43 pub bus_address: Option<&'a str>,
45 pub config_home: Option<&'a Path>,
47 pub home: Option<&'a Path>,
49 pub own_executable: Option<&'a Path>,
51 pub real_git: &'a Path,
53 pub git_dir: &'a Path,
55}
56
57#[derive(Clone, Debug, Eq, PartialEq)]
59pub struct Route {
60 pub address: Bind,
62 pub provision: Option<ProvisionHost>,
65}
66
67pub fn user_unit_dir(config_home: Option<&Path>, home: Option<&Path>) -> Option<PathBuf> {
69 return config_home
70 .map(|directory| { return directory.join("systemd").join("user"); })
71 .or_else(|| { return home.map(|directory| { return directory.join(".config").join("systemd").join("user"); }); });
72}
73
74pub fn choose_route(inputs: &RouteInputs<'_>) -> Option<Route> {
88 let runtime_dir = inputs.runtime_dir?;
89 let socket: PathBuf = socket_for(runtime_dir, inputs.git_dir);
90 let socket_exists: bool = socket.exists();
91 let Some(bus) = session_bus_path(inputs.bus_address, Some(runtime_dir)).filter(|path| { return path.exists(); }) else {
92 return if socket_exists { Some(Route { address: Bind::Path(socket), provision: None }) } else { None };
93 };
94 let Some(service_binary) = inputs
95 .own_executable
96 .and_then(Path::parent)
97 .map(|directory| { return directory.join(SERVICE_PROGRAM); })
98 .filter(|path| { return path.is_file(); })
99 else {
100 return if socket_exists { Some(Route { address: Bind::Path(socket), provision: None }) } else { None };
101 };
102 let host: ProvisionHost = ProvisionHost {
103 runtime_dir: runtime_dir.to_path_buf(),
104 user_unit_dir: user_unit_dir(inputs.config_home, inputs.home),
105 bus,
106 service_binary,
107 git: inputs.real_git.to_path_buf(),
108 instance: instance_of(inputs.git_dir),
109 retirement_wait: RETIREMENT_WAIT,
110 };
111 return Some(Route { address: Bind::Path(socket), provision: Some(host) });
112}
113
114#[derive(Clone, Debug, Eq, PartialEq)]
116pub struct StatusReport {
117 pub code: i32,
119 pub stdout: String,
121 pub stderr: String,
123}
124
125pub fn worktree_git_dir(context: &GitContext, cwd: &Path) -> Option<PathBuf> {
137 if let Some(layout) = read_layout_in(cwd, context.overlay.as_slice()) {
138 return Some(layout.git_dir);
139 }
140 let output = run_git(context, &GitRequest::new(cwd, &["rev-parse", "--path-format=absolute", "--absolute-git-dir"])).ok()?;
141 if !output.succeeded() {
142 return None;
143 }
144 let text: String = String::from_utf8_lossy(output.stdout.as_slice()).trim().to_string();
145 return if text.is_empty() { None } else { Some(PathBuf::from(text)) };
146}
147
148pub fn route_from_environment(real_git: &Path, git_dir: &Path) -> Option<Route> {
158 let path_variable = |name: &str| -> Option<PathBuf> { return std::env::var_os(name).filter(|value| { return !value.is_empty(); }).map(PathBuf::from); };
159 let runtime: Option<PathBuf> = path_variable("XDG_RUNTIME_DIR");
160 let config_home: Option<PathBuf> = path_variable("XDG_CONFIG_HOME");
161 let home: Option<PathBuf> = path_variable("HOME");
162 let bus_address: Option<String> = std::env::var("DBUS_SESSION_BUS_ADDRESS").ok();
163 return choose_route(&RouteInputs {
164 runtime_dir: runtime.as_deref(),
165 bus_address: bus_address.as_deref(),
166 config_home: config_home.as_deref(),
167 home: home.as_deref(),
168 own_executable: remembered_own_executable(),
169 real_git,
170 git_dir,
171 });
172}
173
174pub fn service_status(runtime_dir: Option<&Path>, git_dir: Option<&Path>) -> StatusReport {
186 let answers = |socket: &Path| -> bool { return std::os::unix::net::UnixStream::connect(socket).is_ok(); };
187 let (answered, what): (bool, &str) = match (runtime_dir, git_dir) {
188 (Some(runtime), Some(git)) => (answers(socket_for(runtime, git).as_path()), "The commit service of this worktree"),
189 (Some(runtime), None) => {
190 let any: bool = std::fs::read_dir(runtime.join(SOCKET_DIRECTORY)).is_ok_and(|entries| {
191 return entries.flatten().any(|entry| { return entry.path().extension().is_some_and(|extension| { return extension == "sock"; }) && answers(entry.path().as_path()); });
192 });
193 (any, "A commit service")
194 }
195 (None, _) => (false, "The commit service"),
196 };
197 return if answered {
198 StatusReport { code: 0, stdout: format!("{what} is running and answers.\n"), stderr: String::new() }
199 } else {
200 StatusReport {
201 code: 1,
202 stdout: String::new(),
203 stderr: format!(
204 "{what} does not answer here. It sets itself up the first time a commit needs it, so there is nothing to \
205 install; if a commit fails, its message says what to do.\n"
206 ),
207 }
208 };
209}
210
211#[cfg(test)]
213#[path = "service_route_tests.rs"]
214mod tests;