fix: batch of quick fixes (#417, #338, #330, #358, #419, #344) (#428)

- #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:
Zaki Manian
2026-03-01 09:01:07 +00:00
committed by GitHub
co-authored by Claude Opus 4.6
parent fa52df593d
commit 7481aea083
8 changed files with 255 additions and 21 deletions
+103 -2
View File
@@ -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"))?;
+9
View File
@@ -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();