use std::collections::BTreeSet;
use std::path::PathBuf;
use crate::backends::apt_mirror::{
apply_apt_mirror, apt_mirror_preview, check_apt_mirror, check_current_apt_sources,
};
use crate::config::schema::NetworkPolicy;
use crate::error::ForgeError;
use crate::fsutil::create_dir_all;
use crate::model::{InstallConfig, InstallOptions};
use crate::paths::app_home;
use crate::planning::ExecutionPlan;
use crate::ui::input::{PromptOutcome, confirm};
use crate::ui::{self, StatusKind};
pub(crate) struct AptSourceOverride {
pub(crate) source_file: PathBuf,
pub(crate) lists_dir: PathBuf,
}
pub(crate) fn configure_for_install(
plan: &ExecutionPlan,
config: &InstallConfig,
options: &InstallOptions,
required_packages: &BTreeSet<String>,
) -> Result<Option<AptSourceOverride>, ForgeError> {
if required_packages.is_empty()
|| plan.policy.network != NetworkPolicy::Online
|| !available_for_install(config)
{
return Ok(None);
}
let preview = apt_mirror_preview(config.apt_mirror.as_ref())?;
ui::stdout_status(
StatusKind::Info,
"Checking the current APT sources in isolated state.",
);
match check_current_apt_sources(&preview, required_packages, |message| {
ui::stderr_status(StatusKind::Warning, message)
}) {
Ok(()) => {
ui::stdout_status(
StatusKind::Success,
"Current APT sources are reachable; mirror configuration skipped.",
);
return Ok(None);
}
Err(error) => ui::stderr_status(
StatusKind::Warning,
&format!("Current APT source check failed: {error}"),
),
}
if options.yes {
ui::stdout_status(
StatusKind::Info,
"APT mirror configuration skipped because writing system sources requires interactive confirmation.",
);
return Ok(None);
}
if !prompt_confirmed(confirm(
"The current APT source check failed. Validate and write the configured replacement mirror? Existing sources will not be removed or disabled.",
false,
)?)? {
ui::stdout_status(StatusKind::Info, "APT mirror configuration skipped.");
return Ok(None);
}
ui::stdout_status(
StatusKind::Info,
&format!("Validating APT mirror: {}", preview.source_file.display()),
);
check_apt_mirror(&preview, |message| {
ui::stderr_status(StatusKind::Warning, message)
})?;
apply_apt_mirror(&preview)?;
ui::stdout_status(
StatusKind::Success,
&format!("APT mirror configured: {}", preview.source_file.display()),
);
let lists_dir = app_home().join("apt").join("lists");
create_dir_all(&lists_dir.join("partial"))?;
Ok(Some(AptSourceOverride {
source_file: preview.source_file,
lists_dir,
}))
}
fn available_for_install(config: &InstallConfig) -> bool {
cfg!(target_os = "linux")
&& config
.apt_mirror
.as_ref()
.is_some_and(|mirror| mirror.uri.is_some() || !mirror.rules.is_empty())
}
fn prompt_confirmed(outcome: PromptOutcome<()>) -> Result<bool, ForgeError> {
match outcome {
PromptOutcome::Confirmed(()) => Ok(true),
PromptOutcome::Cancelled => Ok(false),
PromptOutcome::Interrupted => Err(ForgeError::Command(
"operation interrupted by Ctrl-C".to_string(),
)),
PromptOutcome::Unavailable => Err(ForgeError::Command(
"a non-interactive terminal cannot confirm this operation; pass --yes explicitly"
.to_string(),
)),
}
}
#[cfg(test)]
mod tests {
use crate::execution::apt_mirror::prompt_confirmed;
use crate::ui::input::PromptOutcome;
#[test]
fn mirror_replacement_requires_an_explicit_confirmation() {
assert!(prompt_confirmed(PromptOutcome::Confirmed(())).unwrap());
assert!(!prompt_confirmed(PromptOutcome::Cancelled).unwrap());
assert!(prompt_confirmed(PromptOutcome::Interrupted).is_err());
assert!(prompt_confirmed(PromptOutcome::Unavailable).is_err());
}
}