#[cfg(not(target_os = "windows"))]
use crate::args::i18n;
#[cfg(not(target_os = "windows"))]
use crate::args::utils;
#[cfg(not(target_os = "windows"))]
use pacsea::install::shell_single_quote;
#[cfg(not(target_os = "windows"))]
use pacsea::state::SecureString;
#[cfg(not(target_os = "windows"))]
use pacsea::theme;
#[cfg(not(target_os = "windows"))]
use std::path::Path;
#[cfg(not(target_os = "windows"))]
use tracing::{debug, warn};
#[cfg(not(target_os = "windows"))]
fn colorize(text: &str, color_code: &str, no_color: bool) -> String {
if no_color {
text.to_string()
} else {
format!("\x1b[{color_code}m{text}\x1b[0m")
}
}
#[cfg(not(target_os = "windows"))]
fn success_color(text: &str, no_color: bool) -> String {
colorize(text, "32", no_color) }
#[cfg(not(target_os = "windows"))]
fn error_color(text: &str, no_color: bool) -> String {
colorize(text, "31", no_color) }
#[cfg(not(target_os = "windows"))]
fn info_color(text: &str, no_color: bool) -> String {
colorize(text, "36", no_color) }
#[cfg(not(target_os = "windows"))]
fn warning_color(text: &str, no_color: bool) -> String {
colorize(text, "33", no_color) }
#[cfg(not(target_os = "windows"))]
fn format_clickable_path(path: &Path) -> String {
let absolute_path = if path.exists() {
path.canonicalize().unwrap_or_else(|_| {
std::env::current_dir()
.ok()
.and_then(|cwd| cwd.join(path).canonicalize().ok())
.unwrap_or_else(|| path.to_path_buf())
})
} else {
if path.is_absolute() {
path.to_path_buf()
} else {
std::env::current_dir()
.ok()
.map_or_else(|| path.to_path_buf(), |cwd| cwd.join(path))
}
};
let path_str = absolute_path.to_string_lossy();
let file_url = format!("file://{path_str}");
format!("\x1b]8;;{file_url}\x1b\\{path_str}\x1b]8;;\x1b\\")
}
#[cfg(not(target_os = "windows"))]
#[allow(clippy::similar_names)]
fn extract_failed_packages_from_pacman(output: &str) -> Vec<String> {
let mut failed = Vec::new();
let lines: Vec<&str> = output.lines().collect();
let mut in_error_section = false;
let mut in_conflict_section = false;
let target_not_found = i18n::t("app.cli.update.pacman_errors.target_not_found").to_lowercase();
let failed_to_commit = i18n::t("app.cli.update.pacman_errors.failed_to_commit").to_lowercase();
let failed_to_prepare =
i18n::t("app.cli.update.pacman_errors.failed_to_prepare").to_lowercase();
let error_prefix = i18n::t("app.cli.update.pacman_errors.error_prefix").to_lowercase();
let resolving = i18n::t("app.cli.update.pacman_errors.resolving").to_lowercase();
let looking_for = i18n::t("app.cli.update.pacman_errors.looking_for").to_lowercase();
let package_word = i18n::t("app.cli.update.pacman_errors.package").to_lowercase();
let packages_word = i18n::t("app.cli.update.pacman_errors.packages").to_lowercase();
let error_word = i18n::t("app.cli.update.pacman_errors.error").to_lowercase();
let failed_word = i18n::t("app.cli.update.pacman_errors.failed").to_lowercase();
let transaction_word = i18n::t("app.cli.update.pacman_errors.transaction").to_lowercase();
let conflicting_word = i18n::t("app.cli.update.pacman_errors.conflicting").to_lowercase();
let files_word = i18n::t("app.cli.update.pacman_errors.files").to_lowercase();
for line in &lines {
let trimmed = line.trim();
let lower = trimmed.to_lowercase();
if lower.contains(&target_not_found) {
if let Some(colon_pos) = trimmed.rfind(':') {
let after_colon = &trimmed[colon_pos + 1..].trim();
if after_colon
.chars()
.all(|c| c.is_alphanumeric() || c == '-' || c == '_' || c == '/')
{
let pkg = after_colon.trim_end_matches(|c: char| {
!c.is_alphanumeric() && c != '-' && c != '_' && c != '/'
});
if !pkg.is_empty() && pkg.len() > 1 {
failed.push(pkg.to_string());
}
}
}
in_error_section = true;
}
else if lower.contains(&failed_to_commit) || lower.contains(&failed_to_prepare) {
in_error_section = true;
in_conflict_section = true;
}
else if in_error_section || in_conflict_section {
if !trimmed.is_empty()
&& !lower.starts_with(&format!("{error_prefix}:"))
&& !lower.contains(&resolving)
&& !lower.contains(&looking_for)
&& !lower.contains("::")
{
let words: Vec<&str> = trimmed.split_whitespace().collect();
for word in words {
let clean_word = word.trim_matches(|c: char| {
!c.is_alphanumeric() && c != '-' && c != '_' && c != '/' && c != ':'
});
if clean_word.len() >= 2
&& clean_word.chars().all(|c| {
c.is_alphanumeric() || c == '-' || c == '_' || c == '/' || c == ':'
})
&& clean_word.contains(|c: char| c.is_alphanumeric())
{
if !clean_word.eq_ignore_ascii_case(&package_word)
&& !clean_word.eq_ignore_ascii_case(&packages_word)
&& !clean_word.eq_ignore_ascii_case(&error_word)
&& !clean_word.eq_ignore_ascii_case(&failed_word)
&& !clean_word.eq_ignore_ascii_case(&transaction_word)
&& !clean_word.eq_ignore_ascii_case(&conflicting_word)
&& !clean_word.eq_ignore_ascii_case(&files_word)
{
failed.push(clean_word.to_string());
}
}
}
}
if trimmed.is_empty() || lower.starts_with(&format!("{error_prefix}:")) {
in_error_section = false;
in_conflict_section = false;
}
}
else if trimmed.contains("::") {
let parts: Vec<&str> = trimmed.split("::").collect();
if parts.len() == 2 {
let pkg_part = parts[1].split_whitespace().next().unwrap_or("");
if pkg_part
.chars()
.all(|c| c.is_alphanumeric() || c == '-' || c == '_')
&& pkg_part.len() >= 2
{
failed.push(pkg_part.to_string());
}
}
}
}
failed
}
#[cfg(not(target_os = "windows"))]
fn extract_failed_packages(output: &str, helper: &str) -> Vec<String> {
let mut failed = if helper == "pacman" {
extract_failed_packages_from_pacman(output)
} else {
let mut failed_aur = Vec::new();
let lines: Vec<&str> = output.lines().collect();
for line in &lines {
if line.contains(" - exit status")
&& let Some(pkg) = line.split(" - exit status").next()
{
let pkg = pkg.trim();
let pkg = pkg.strip_prefix("->").unwrap_or(pkg).trim();
if !pkg.is_empty() {
failed_aur.push(pkg.to_string());
}
}
}
if failed_aur.is_empty() {
let mut in_package_list = false;
for line in &lines {
let trimmed = line.trim();
if trimmed.starts_with("->") && trimmed.len() > 2 {
let after_arrow = &trimmed[2..].trim();
if after_arrow.chars().all(|c| !c.is_whitespace() && c != ':') {
if after_arrow
.chars()
.all(|c| c.is_alphanumeric() || c == '-' || c == '_')
{
failed_aur.push((*after_arrow).to_string());
in_package_list = true;
}
} else {
in_package_list = true;
}
} else if in_package_list {
if !trimmed.is_empty()
&& !trimmed.starts_with("==>")
&& !trimmed.contains("exit status")
&& trimmed
.chars()
.all(|c| c.is_alphanumeric() || c == '-' || c == '_')
{
failed_aur.push(trimmed.to_string());
} else if trimmed.is_empty() || trimmed.starts_with("==>") {
in_package_list = false;
}
}
}
}
failed_aur
};
failed.sort();
failed.dedup();
failed.retain(|pkg| {
pkg.len() >= 2
&& !pkg.eq_ignore_ascii_case("package")
&& !pkg.eq_ignore_ascii_case("packages")
&& !pkg.eq_ignore_ascii_case("error")
&& !pkg.eq_ignore_ascii_case("failed")
});
failed
}
#[cfg(not(target_os = "windows"))]
fn run_command_with_logging(
program: &str,
args: &[&str],
log_file_path: &Path,
password: Option<&str>,
interactive_auth: bool,
) -> Result<(std::process::ExitStatus, String), std::io::Error> {
use std::io::IsTerminal;
use std::process::{Command, Stdio};
let log_file_str = log_file_path.to_string_lossy();
let args_str = args
.iter()
.map(|a| shell_single_quote(a))
.collect::<Vec<_>>()
.join(" ");
let has_tty = std::io::stdout().is_terminal();
let tty_redirect = if has_tty {
"> /dev/tty"
} else {
"> /dev/stdout"
};
let temp_output =
std::env::temp_dir().join(format!("pacsea_update_output_{}.txt", std::process::id()));
let temp_output_str = temp_output.to_string_lossy();
let tool = pacsea::logic::privilege::active_tool().map_err(std::io::Error::other)?;
let full_command = if program == tool.binary_name() {
password.map_or_else(
|| pacsea::logic::privilege::build_privilege_command(tool, &args_str),
|pass| {
args.first().map_or_else(
|| {
pacsea::logic::privilege::build_password_pipe(tool, pass, &args_str)
.unwrap_or_else(|| {
pacsea::logic::privilege::build_privilege_command(tool, &args_str)
})
},
|cmd| {
let cmd_escaped = shell_single_quote(cmd);
let cmd_args = &args[1..];
let cmd_args_str = cmd_args
.iter()
.map(|a| shell_single_quote(a))
.collect::<Vec<_>>()
.join(" ");
let base = if cmd_args_str.is_empty() {
cmd_escaped
} else {
format!("{cmd_escaped} {cmd_args_str}")
};
pacsea::logic::privilege::build_password_pipe(tool, pass, &base)
.unwrap_or_else(|| {
pacsea::logic::privilege::build_privilege_command(tool, &base)
})
},
)
},
)
} else {
let program_escaped = shell_single_quote(program);
format!("{program_escaped} {args_str}")
};
let log_file_escaped = shell_single_quote(&log_file_str);
let temp_output_escaped = shell_single_quote(&temp_output_str);
let shell_cmd = format!(
"set -o pipefail; stdbuf -oL -eL {full_command} 2>&1 | tee -a {log_file_escaped} | tee {temp_output_escaped} {tty_redirect}"
);
let shell_cmd_log = if program == tool.binary_name() && password.is_some() {
let bin = tool.binary_name();
format!(
"set -o pipefail; stdbuf -oL -eL {bin} {args_str} 2>&1 | tee -a {log_file_escaped} | tee {temp_output_escaped} {tty_redirect}"
)
} else {
shell_cmd.clone()
};
debug!(
program,
args = ?args,
uses_password = password.is_some(),
has_tty,
log_file = %log_file_path.display(),
temp_output = %temp_output.display(),
shell_cmd = %shell_cmd_log,
"executing update command with logging"
);
let stdin_cfg = if interactive_auth {
Stdio::inherit()
} else {
Stdio::null()
};
let status = Command::new("bash")
.arg("-c")
.arg(&shell_cmd)
.env("LC_ALL", "C")
.env("LANG", "C")
.stdin(stdin_cfg)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.status()
.map_err(|err| {
warn!(
program,
args = ?args,
log_file = %log_file_path.display(),
error = %err,
"failed to spawn update command"
);
err
})?;
let output = match std::fs::read_to_string(&temp_output) {
Ok(content) => content,
Err(err) => {
warn!(
path = %temp_output.display(),
error = %err,
"failed to read update temp output"
);
String::new()
}
};
if let Err(err) = std::fs::remove_file(&temp_output) {
debug!(
path = %temp_output.display(),
error = %err,
"failed to remove temp output file"
);
}
debug!(
program,
args = ?args,
status = ?status,
status_code = status.code(),
output_len = output.len(),
log_file = %log_file_path.display(),
"update command finished"
);
Ok((status, output))
}
#[cfg(not(target_os = "windows"))]
struct UpdateState {
all_succeeded: bool,
failed_commands: Vec<String>,
failed_packages: Vec<String>,
pacman_succeeded: Option<bool>,
aur_succeeded: Option<bool>,
aur_helper_name: Option<String>,
}
#[cfg(not(target_os = "windows"))]
impl UpdateState {
const fn new() -> Self {
Self {
all_succeeded: true,
failed_commands: Vec::new(),
failed_packages: Vec::new(),
pacman_succeeded: None,
aur_succeeded: None,
aur_helper_name: None,
}
}
}
#[cfg(not(target_os = "windows"))]
fn prompt_and_validate_password(write_log: &(dyn Fn(&str) + Send + Sync)) -> Option<SecureString> {
use std::io::IsTerminal;
let settings = theme::settings();
let auth_mode = pacsea::logic::password::resolve_auth_mode(&settings);
match auth_mode {
pacsea::logic::privilege::AuthMode::Interactive => {
write_log(
"Auth mode is 'interactive'; skipping password prompt (privilege tool handles auth)",
);
return None;
}
pacsea::logic::privilege::AuthMode::PasswordlessOnly => {
if pacsea::logic::password::should_use_passwordless_sudo(&settings) {
write_log("Passwordless privilege enabled and available, skipping password prompt");
return None;
}
}
pacsea::logic::privilege::AuthMode::Prompt => {}
}
if !std::io::stdin().is_terminal() {
let error_msg =
"Password required but stdin is not a terminal. Cannot prompt for password.";
eprintln!("{}", i18n::t_fmt1("app.cli.update.error_prefix", error_msg));
write_log("FAILED: Password required but stdin is not a terminal");
tracing::error!("Password required but stdin is not a terminal");
std::process::exit(1);
}
let username = std::env::var("USER").unwrap_or_else(|_| "user".to_string());
let password_prompt = i18n::t_fmt1("app.cli.update.password_prompt", &username);
match rpassword::prompt_password(&password_prompt) {
Ok(pass) => {
let trimmed_pass = pass.trim();
if trimmed_pass.is_empty() {
let error_msg = "Empty password provided. Password cannot be empty.";
eprintln!("{}", i18n::t_fmt1("app.cli.update.error_prefix", error_msg));
write_log("FAILED: Empty password provided");
tracing::error!("Empty password provided");
std::process::exit(1);
}
write_log("Password obtained from user (not logged)");
Some(SecureString::from(trimmed_pass))
}
Err(e) => {
eprintln!("{}", i18n::t_fmt1("app.cli.update.error_prefix", &e));
write_log(&format!("FAILED: Could not read password: {e}"));
tracing::error!("Failed to read sudo password: {e}");
std::process::exit(1);
}
}
}
#[cfg(not(target_os = "windows"))]
fn setup_log_file(log_file_path: &std::path::Path) -> Box<dyn Fn(&str) + Send + Sync> {
use std::fs::OpenOptions;
use std::io::Write;
use std::time::{SystemTime, UNIX_EPOCH};
if let Some(parent) = log_file_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
let log_path = log_file_path.to_path_buf();
Box::new(move |message: &str| {
if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(&log_path) {
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).map_or_else(
|_| "unknown".to_string(),
|d| pacsea::util::ts_to_date(Some(i64::try_from(d.as_secs()).unwrap_or(0))),
);
let _ = writeln!(file, "[{timestamp}] {message}");
}
})
}
#[cfg(not(target_os = "windows"))]
fn run_pacman_update(
state: &mut UpdateState,
log_file_path: &Path,
password: Option<&str>,
no_color: bool,
interactive_auth: bool,
write_log: &(dyn Fn(&str) + Send + Sync),
) {
println!(
"{}",
info_color(&i18n::t("app.cli.update.starting"), no_color)
);
write_log("Starting system update: pacman -Syu --noconfirm");
let tool = match pacsea::logic::privilege::active_tool() {
Ok(t) => t,
Err(err) => {
println!(
"{}",
error_color(&i18n::t("app.cli.update.pacman_exec_failed"), no_color)
);
eprintln!("{}", error_color(&err, no_color));
write_log(&format!("FAILED: Could not resolve privilege tool: {err}"));
state.all_succeeded = false;
state
.failed_commands
.push("pacman -Syu --noconfirm".to_string());
state.pacman_succeeded = Some(false);
return;
}
};
let pacman_result = run_command_with_logging(
tool.binary_name(),
&["pacman", "-Syu", "--noconfirm"],
log_file_path,
password,
interactive_auth,
);
match pacman_result {
Ok((status, output)) => {
if status.success() {
println!(
"{}",
success_color(&i18n::t("app.cli.update.pacman_success"), no_color)
);
write_log("SUCCESS: pacman -Syu --noconfirm completed successfully");
state.pacman_succeeded = Some(true);
} else {
println!(
"{}",
error_color(&i18n::t("app.cli.update.pacman_failed"), no_color)
);
write_log(&format!(
"FAILED: pacman -Syu --noconfirm failed with exit code {:?}",
status.code()
));
let packages = extract_failed_packages(&output, "pacman");
state.failed_packages.extend(packages);
state.all_succeeded = false;
state.failed_commands.push("pacman -Syu".to_string());
state.pacman_succeeded = Some(false);
}
}
Err(e) => {
println!(
"{}",
error_color(&i18n::t("app.cli.update.pacman_exec_failed"), no_color)
);
eprintln!(
"{}",
error_color(&i18n::t_fmt1("app.cli.update.error_prefix", &e), no_color)
);
write_log(&format!(
"FAILED: Could not execute pacman -Syu --noconfirm: {e}"
));
state.all_succeeded = false;
state
.failed_commands
.push("pacman -Syu --noconfirm".to_string());
state.pacman_succeeded = Some(false);
}
}
}
#[cfg(not(target_os = "windows"))]
fn refresh_sudo_timestamp(password: Option<&str>, write_log: &(dyn Fn(&str) + Send + Sync)) {
use std::process::Command;
if let Some(pass) = password {
let Ok(tool) = pacsea::logic::privilege::active_tool() else {
return;
};
if let Some(warmup) = pacsea::logic::privilege::build_credential_warmup(tool, pass) {
let _ = Command::new("bash")
.arg("-c")
.arg(&warmup)
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status();
write_log(&format!(
"Refreshed {} credential timestamp for AUR helper",
tool.binary_name()
));
}
}
}
#[cfg(not(target_os = "windows"))]
fn run_aur_update(
state: &mut UpdateState,
log_file_path: &Path,
no_color: bool,
interactive_auth: bool,
write_log: &(dyn Fn(&str) + Send + Sync),
) {
let aur_helper = utils::get_aur_helper();
if let Some(helper) = aur_helper {
state.aur_helper_name = Some(helper.to_string());
println!(
"\n{}",
info_color(
&i18n::t_fmt1("app.cli.update.aur_starting", helper),
no_color
)
);
write_log(&format!("Starting AUR update: {helper} -Sua --noconfirm"));
let aur_result = run_command_with_logging(
helper,
&["-Sua", "--noconfirm"],
log_file_path,
None, interactive_auth,
);
match aur_result {
Ok((status, output)) => {
if status.success() {
println!(
"{}",
success_color(
&i18n::t_fmt1("app.cli.update.aur_success", helper),
no_color
)
);
write_log(&format!(
"SUCCESS: {helper} -Sua --noconfirm completed successfully"
));
state.aur_succeeded = Some(true);
} else {
println!(
"{}",
error_color(&i18n::t_fmt1("app.cli.update.aur_failed", helper), no_color)
);
write_log(&format!(
"FAILED: {} -Sua --noconfirm failed with exit code {:?}",
helper,
status.code()
));
let packages = extract_failed_packages(&output, helper);
state.failed_packages.extend(packages);
state.all_succeeded = false;
state
.failed_commands
.push(format!("{helper} -Sua --noconfirm"));
state.aur_succeeded = Some(false);
}
}
Err(e) => {
println!(
"{}",
error_color(
&i18n::t_fmt1("app.cli.update.aur_exec_failed", helper),
no_color
)
);
eprintln!(
"{}",
error_color(&i18n::t_fmt1("app.cli.update.error_prefix", &e), no_color)
);
write_log(&format!(
"FAILED: Could not execute {helper} -Sua --noconfirm: {e}"
));
state.all_succeeded = false;
state
.failed_commands
.push(format!("{helper} -Sua --noconfirm"));
state.aur_succeeded = Some(false);
}
}
} else {
println!(
"\n{}",
warning_color(&i18n::t("app.cli.update.no_aur_helper"), no_color)
);
write_log("SKIPPED: No AUR helper (paru/yay) available");
}
}
#[cfg(not(target_os = "windows"))]
fn display_update_summary(
state: &UpdateState,
log_file_path: &Path,
no_color: bool,
write_log: &(dyn Fn(&str) + Send + Sync),
) {
println!(
"\n{}",
info_color(&i18n::t("app.cli.update.separator"), no_color)
);
if state.pacman_succeeded == Some(true) {
println!(
"{}",
success_color(&i18n::t("app.cli.update.pacman_success"), no_color)
);
} else if state.pacman_succeeded == Some(false) {
println!(
"{}",
error_color(&i18n::t("app.cli.update.pacman_failed"), no_color)
);
}
if let Some(helper) = &state.aur_helper_name {
if state.aur_succeeded == Some(true) {
println!(
"{}",
success_color(
&i18n::t_fmt1("app.cli.update.aur_success", helper),
no_color
)
);
} else if state.aur_succeeded == Some(false) {
println!(
"{}",
error_color(&i18n::t_fmt1("app.cli.update.aur_failed", helper), no_color)
);
}
}
if state.all_succeeded {
println!(
"\n{}",
success_color(&i18n::t("app.cli.update.all_success"), no_color)
);
write_log("SUMMARY: All updates completed successfully");
} else {
println!(
"\n{}",
error_color(&i18n::t("app.cli.update.completed_with_errors"), no_color)
);
println!(
"\n{}",
info_color(&i18n::t("app.cli.update.failure_summary"), no_color)
);
if state.pacman_succeeded == Some(false) {
println!(
" {} {}",
error_color("✗", no_color),
error_color(&i18n::t("app.cli.update.pacman_failed"), no_color)
);
}
if let Some(helper) = &state.aur_helper_name {
if state.aur_succeeded == Some(false) {
println!(
" {} {}",
error_color("✗", no_color),
error_color(&i18n::t_fmt1("app.cli.update.aur_failed", helper), no_color)
);
} else if state.pacman_succeeded == Some(false) {
println!(
" {} {}",
warning_color("⊘", no_color),
warning_color(
&i18n::t("app.cli.update.aur_skipped_pacman_failed"),
no_color
)
);
}
}
if !state.failed_commands.is_empty() {
println!(
"\n{}",
warning_color(&i18n::t("app.cli.update.failed_commands"), no_color)
);
for cmd in &state.failed_commands {
println!(" - {}", error_color(cmd, no_color));
}
}
if !state.failed_packages.is_empty() {
println!(
"\n{}",
warning_color(&i18n::t("app.cli.update.failed_packages"), no_color)
);
for pkg in &state.failed_packages {
println!(" - {}", error_color(pkg, no_color));
}
write_log(&i18n::t_fmt1(
"app.cli.update.failed_packages_log",
format!("{:?}", state.failed_packages),
));
}
write_log(&format!(
"SUMMARY: Update failed. Failed commands: {:?}",
state.failed_commands
));
}
let log_file_format = i18n::t("app.cli.update.log_file");
let clickable_path = format_clickable_path(log_file_path);
let log_file_message = log_file_format.replace("{}", &clickable_path);
println!("{log_file_message}");
write_log(&format!(
"Update process finished. Log file: {}",
log_file_path.display()
));
}
#[cfg(not(target_os = "windows"))]
pub fn handle_update(no_color: bool) -> ! {
tracing::info!("System update requested from CLI");
let logs_dir = theme::logs_dir();
let log_file_path = logs_dir.join("update.log");
let write_log = setup_log_file(&log_file_path);
let password = prompt_and_validate_password(&*write_log);
let settings = theme::settings();
let interactive_auth = pacsea::logic::password::resolve_auth_mode(&settings)
== pacsea::logic::privilege::AuthMode::Interactive;
let readiness = pacsea::logic::long_run_auth::evaluate_long_run_auth_readiness(&settings);
if readiness.should_warn {
println!(
"{}",
warning_color(&i18n::t("app.cli.update.long_run_auth_warning"), no_color)
);
write_log("WARN: long-run auth readiness indicates possible mid-run re-auth prompt");
}
let mut state = UpdateState::new();
run_pacman_update(
&mut state,
&log_file_path,
password.as_deref(),
no_color,
interactive_auth,
&*write_log,
);
if !interactive_auth {
refresh_sudo_timestamp(password.as_deref(), &*write_log);
}
if state.pacman_succeeded == Some(true) {
run_aur_update(
&mut state,
&log_file_path,
no_color,
interactive_auth,
&*write_log,
);
} else {
println!(
"\n{}",
warning_color(
&i18n::t("app.cli.update.aur_skipped_pacman_failed"),
no_color
)
);
write_log("SKIPPED: AUR update skipped because pacman update failed");
}
display_update_summary(&state, &log_file_path, no_color, &*write_log);
if state.all_succeeded {
tracing::info!("System update completed successfully");
std::process::exit(0);
} else {
tracing::error!("System update completed with errors");
std::process::exit(1);
}
}