mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-26 15:40:18 +00:00
- #417: Add Docker auto-start login item hint for macOS in setup wizard - #338: Add clippy.toml with complexity thresholds for AI-assisted dev - #330: Add structured FallbackFailed error variant to ExtensionError - #358: Revoke credential mappings on extension removal (SharedCredentialRegistry) - #419: Detect conflicting cloudflared services during tunnel setup - #344: Improve embedding auth failure warning with configuration hint Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
fa52df593d
commit
7481aea083
+103
-2
@@ -20,8 +20,8 @@ use crate::secrets::SecretsCrypto;
|
||||
use crate::secrets::{CreateSecretParams, SecretsStore};
|
||||
use crate::settings::{Settings, TunnelSettings};
|
||||
use crate::setup::prompts::{
|
||||
confirm, input, optional_input, print_error, print_info, print_success, secret_input,
|
||||
select_one,
|
||||
confirm, input, optional_input, print_error, print_info, print_success, print_warning,
|
||||
secret_input, select_one,
|
||||
};
|
||||
|
||||
/// Typed errors for channel setup flows.
|
||||
@@ -38,6 +38,9 @@ pub enum ChannelSetupError {
|
||||
|
||||
#[error("{0}")]
|
||||
Validation(String),
|
||||
|
||||
#[error("Setup cancelled by user")]
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
/// Context for saving secrets during setup.
|
||||
@@ -490,6 +493,15 @@ fn setup_tunnel_cloudflare() -> Result<TunnelSettings, ChannelSetupError> {
|
||||
}
|
||||
}
|
||||
|
||||
// Detect existing cloudflared services that may conflict
|
||||
if let Some(warning) = detect_existing_cloudflared() {
|
||||
print_warning(&warning);
|
||||
if !confirm("Continue anyway?", true)? {
|
||||
return Err(ChannelSetupError::Cancelled);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
print_info("Get your tunnel token from the Cloudflare Zero Trust dashboard:");
|
||||
print_info(" https://one.dash.cloudflare.com/ > Networks > Tunnels");
|
||||
println!();
|
||||
@@ -529,6 +541,95 @@ fn setup_tunnel_cloudflare() -> Result<TunnelSettings, ChannelSetupError> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Detect running cloudflared processes or managed services that could conflict
|
||||
/// with IronClaw's tunnel management.
|
||||
fn detect_existing_cloudflared() -> Option<String> {
|
||||
let mut conflicts = Vec::new();
|
||||
|
||||
// Check for running cloudflared processes (all platforms)
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let output = std::process::Command::new("pgrep")
|
||||
.args(["-x", "cloudflared"])
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.output();
|
||||
if let Ok(out) = output
|
||||
&& out.status.success()
|
||||
{
|
||||
let pids = String::from_utf8_lossy(&out.stdout);
|
||||
let pids: Vec<&str> = pids.trim().lines().collect();
|
||||
if !pids.is_empty() {
|
||||
conflicts.push(format!(
|
||||
"Running cloudflared process(es): PID {}",
|
||||
pids.join(", ")
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// macOS: check brew services
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let output = std::process::Command::new("brew")
|
||||
.args(["services", "list"])
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.output();
|
||||
if let Ok(out) = output {
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
for line in stdout.lines() {
|
||||
if line.contains("cloudflared") && line.contains("started") {
|
||||
conflicts.push("Homebrew service: cloudflared (started)".to_string());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let output = std::process::Command::new("launchctl")
|
||||
.args(["list"])
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.output();
|
||||
if let Ok(out) = output {
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
for line in stdout.lines() {
|
||||
if line.contains("cloudflared") {
|
||||
conflicts.push("launchd service: cloudflared detected".to_string());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Linux: check systemd
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let output = std::process::Command::new("systemctl")
|
||||
.args(["is-active", "cloudflared"])
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.output();
|
||||
if let Ok(out) = output {
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
if stdout.trim() == "active" {
|
||||
conflicts.push("systemd service: cloudflared (active)".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if conflicts.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(format!(
|
||||
"Detected existing cloudflared service(s) that may conflict:\n {}\n\
|
||||
Consider stopping them first (e.g., `brew services stop cloudflared` or \
|
||||
`sudo systemctl stop cloudflared`).",
|
||||
conflicts.join("\n ")
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_tunnel_tailscale() -> Result<TunnelSettings, ChannelSetupError> {
|
||||
let funnel = confirm("Use Tailscale Funnel (public internet)?", true)?;
|
||||
let hostname = optional_input("Hostname override", Some("leave empty for auto-detect"))?;
|
||||
|
||||
@@ -311,6 +311,15 @@ pub fn print_error(message: &str) {
|
||||
eprintln!(" {}", message);
|
||||
}
|
||||
|
||||
/// Print a warning message with yellow exclamation.
|
||||
pub fn print_warning(message: &str) {
|
||||
let mut stdout = io::stdout();
|
||||
let _ = execute!(stdout, SetForegroundColor(Color::Yellow));
|
||||
print!("!");
|
||||
let _ = execute!(stdout, ResetColor);
|
||||
println!(" {}", message);
|
||||
}
|
||||
|
||||
/// Print an info message with blue info icon.
|
||||
pub fn print_info(message: &str) {
|
||||
let mut stdout = io::stdout();
|
||||
|
||||
Reference in New Issue
Block a user