coding_agent_search/sources/
mod.rs1pub mod config;
72pub mod index;
73pub mod install;
74pub mod interactive;
75pub mod probe;
76pub mod provenance;
77pub mod setup;
78pub mod sync;
79
80use std::io::Read as IoRead;
81use std::process::{Child, Output};
82use std::sync::mpsc::{self, Receiver, RecvTimeoutError};
83use std::time::{Duration, Instant};
84
85use wait_timeout::ChildExt;
86
87pub(crate) const HOST_KEY_VERIFICATION_FAILED: &str = "Host key verification failed";
89
90pub(crate) fn strict_ssh_cli_tokens(connect_timeout_secs: u64) -> Vec<String> {
95 vec![
96 "-o".to_string(),
97 "BatchMode=yes".to_string(),
98 "-o".to_string(),
99 format!("ConnectTimeout={connect_timeout_secs}"),
100 "-o".to_string(),
101 "ServerAliveInterval=15".to_string(),
102 "-o".to_string(),
103 "ServerAliveCountMax=3".to_string(),
104 "-o".to_string(),
105 "StrictHostKeyChecking=yes".to_string(),
106 ]
107}
108
109pub(crate) fn strict_ssh_command_for_rsync(connect_timeout_secs: u64) -> String {
111 format!(
112 "ssh -o BatchMode=yes -o ConnectTimeout={connect_timeout_secs} -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o StrictHostKeyChecking=yes"
113 )
114}
115
116fn drain_child_pipe<R>(mut pipe: R) -> Receiver<std::io::Result<Vec<u8>>>
117where
118 R: IoRead + Send + 'static,
119{
120 let (sender, receiver) = mpsc::channel();
121 std::thread::spawn(move || {
122 let mut output = Vec::new();
123 let result = pipe.read_to_end(&mut output).map(|_| output);
124 let _ = sender.send(result);
125 });
126 receiver
127}
128
129fn finish_child_pipe(
130 pipe_reader: Option<Receiver<std::io::Result<Vec<u8>>>>,
131 deadline: Instant,
132) -> std::io::Result<Option<Vec<u8>>> {
133 match pipe_reader {
134 Some(reader) => {
135 let remaining = deadline
136 .checked_duration_since(Instant::now())
137 .unwrap_or(Duration::ZERO);
138 match reader.recv_timeout(remaining) {
139 Ok(result) => result.map(Some),
140 Err(RecvTimeoutError::Timeout) => Ok(None),
141 Err(RecvTimeoutError::Disconnected) => Err(std::io::Error::new(
142 std::io::ErrorKind::BrokenPipe,
143 "child pipe reader disconnected before sending output",
144 )),
145 }
146 }
147 None => Ok(Some(Vec::new())),
148 }
149}
150
151pub(crate) fn wait_for_child_output_with_timeout(
154 mut child: Child,
155 timeout: Duration,
156) -> std::io::Result<Option<Output>> {
157 let timeout = if timeout.is_zero() {
158 Duration::from_secs(1)
159 } else {
160 timeout
161 };
162 let start = Instant::now();
163 let deadline = start.checked_add(timeout).unwrap_or(start);
164 let stdout_reader = child.stdout.take().map(drain_child_pipe);
165 let stderr_reader = child.stderr.take().map(drain_child_pipe);
166
167 match child.wait_timeout(timeout)? {
168 Some(status) => {
169 let Some(stdout) = finish_child_pipe(stdout_reader, deadline)? else {
170 return Ok(None);
171 };
172 let Some(stderr) = finish_child_pipe(stderr_reader, deadline)? else {
173 return Ok(None);
174 };
175 Ok(Some(Output {
176 status,
177 stdout,
178 stderr,
179 }))
180 }
181 None => {
182 let _ = child.kill();
183 let _ = child.wait();
184 Ok(None)
185 }
186 }
187}
188
189pub(crate) fn is_host_key_verification_failure(stderr: &str) -> bool {
191 stderr.contains(HOST_KEY_VERIFICATION_FAILED)
192}
193
194pub(crate) fn host_key_verification_error(host: &str) -> String {
196 format!(
197 "Host key verification failed for {host} (add/verify host key in ~/.ssh/known_hosts first)"
198 )
199}
200
201pub use config::{
203 BackupInfo, ConfigError, ConfigPreview, DiscoveredHost, MergeResult, PathMapping, Platform,
204 SkipReason, SourceConfigGenerator, SourceDefinition, SourcesConfig, SyncSchedule,
205 discover_ssh_hosts, get_preset_paths,
206};
207
208pub use provenance::{LOCAL_SOURCE_ID, Origin, Source, SourceFilter, SourceKind};
210
211pub use sync::{
213 PathSyncResult, SourceHealthKind, SourceSyncAction, SourceSyncDecision, SourceSyncInfo,
214 SyncEngine, SyncError, SyncMethod, SyncReport, SyncResult, SyncStatus,
215};
216
217pub use probe::{
219 CassStatus, DetectedAgent, HostProbeResult, ProbeCache, ResourceInfo, SystemInfo, probe_host,
220 probe_hosts_parallel,
221};
222
223pub use install::{
225 InstallError, InstallMethod, InstallProgress, InstallResult, InstallStage, RemoteInstaller,
226};
227
228pub use index::{IndexError, IndexProgress, IndexResult, IndexStage, RemoteIndexer};
230
231pub use interactive::{
233 CassStatusDisplay, HostDisplayInfo, HostSelectionResult, HostSelector, HostState,
234 InteractiveError, confirm_action, confirm_with_details, probe_to_display_info,
235 run_host_selection,
236};
237
238pub use setup::{SetupError, SetupOptions, SetupResult, SetupState, run_setup};