use proxmox_offline_mirror::{
config::{save_config, MediaConfig, MirrorConfig},
mirror,
- types::{ProductType, MIRROR_ID_SCHEMA},
+ types::{ProductType, MEDIA_ID_SCHEMA, MIRROR_ID_SCHEMA},
};
mod proxmox_offline_mirror_cmds;
}
}
-fn action_add_mirror(config: &SectionConfigData) -> Result<MirrorConfig, Error> {
+fn derive_debian_repo(
+ release: &Release,
+ variant: &DebianVariant,
+ components: &str,
+) -> (String, String, String) {
+ let url = match (release, variant) {
+ (Release::Bullseye, DebianVariant::Main) => "http://deb.debian.org/debian bullseye",
+ (Release::Bullseye, DebianVariant::Security) => {
+ "http://deb.debian.org/debian-security bullseye-security"
+ }
+ (Release::Bullseye, DebianVariant::Updates) => {
+ "http://deb.debian.org/debian bullseye-updates"
+ }
+ (Release::Bullseye, DebianVariant::Backports) => {
+ "http://deb.debian.org/debian bullseye-backports"
+ }
+ (Release::Bullseye, DebianVariant::Debug) => {
+ "http://deb.debian.org/debian-debug bullseye-debug"
+ }
+ (Release::Buster, DebianVariant::Main) => "http://deb.debian.org/debian buster",
+ (Release::Buster, DebianVariant::Security) => {
+ "http://deb.debian.org/debian-security buster/updates"
+ }
+ (Release::Buster, DebianVariant::Updates) => "http://deb.debian.org/debian buster-updates",
+ (Release::Buster, DebianVariant::Backports) => {
+ "http://deb.debian.org/debian buster-backports"
+ }
+ (Release::Buster, DebianVariant::Debug) => {
+ "http://deb.debian.org/debian-debug buster-debug"
+ }
+ };
+
+ let url = format!("{url} {components}");
+ let key = match (release, variant) {
+ (Release::Bullseye, DebianVariant::Security) => {
+ "/usr/share/keyrings/debian-archive-bullseye-security-automatic.gpg"
+ }
+ (Release::Bullseye, _) => "/usr/share/keyrings/debian-archive-bullseye-automatic.gpg",
+ (Release::Buster, DebianVariant::Security) => {
+ "/usr/share/keyrings/debian-archive-buster-security-automatic.gpg"
+ }
+ (Release::Buster, _) => "/usr/share/keyrings/debian-archive-buster-stable.gpg",
+ };
+
+ let suggested_id = format!("debian_{release}_{variant}");
+
+ (url, key.to_string(), suggested_id)
+}
+
+fn action_add_mirror(config: &SectionConfigData) -> Result<Vec<MirrorConfig>, Error> {
let mut use_subscription = None;
+ let mut extra_repos = Vec::new();
let (repository, key_path, architectures, suggested_id) = if read_bool_from_tty(
"Guided Setup",
Some(true),
)? {
let distros = &[
- (Distro::Debian, "Debian"),
+ (Distro::Pve, "Proxmox VE"),
(Distro::Pbs, "Proxmox Backup Server"),
(Distro::Pmg, "Proxmox Mail Gateway"),
- (Distro::Pve, "Proxmox VE"),
- (Distro::PveCeph, "Proxmox VE Ceph"),
+ (Distro::PveCeph, "Proxmox Ceph"),
+ (Distro::Debian, "Debian"),
];
let dist = read_selection_from_tty("Select distro to mirror", distros, None)?;
let releases = &[(Release::Bullseye, "Bullseye"), (Release::Buster, "Buster")];
let release = read_selection_from_tty("Select release", releases, Some(0))?;
+ let mut add_debian_repo = false;
+
let (url, key_path, suggested_id) = match dist {
Distro::Debian => {
let variants = &[
Some("main contrib non-free"),
)?;
- let url = match (release, variant) {
- (Release::Bullseye, DebianVariant::Main) => {
- "http://deb.debian.org/debian bullseye"
- }
- (Release::Bullseye, DebianVariant::Security) => {
- "http://deb.debian.org/debian-security bullseye-security"
- }
- (Release::Bullseye, DebianVariant::Updates) => {
- "http://deb.debian.org/debian bullseye-updates"
- }
- (Release::Bullseye, DebianVariant::Backports) => {
- "http://deb.debian.org/debian bullseye-backports"
- }
- (Release::Bullseye, DebianVariant::Debug) => {
- "http://deb.debian.org/debian-debug bullseye-debug"
- }
- (Release::Buster, DebianVariant::Main) => "http://deb.debian.org/debian buster",
- (Release::Buster, DebianVariant::Security) => {
- "http://deb.debian.org/debian-security buster/updates"
- }
- (Release::Buster, DebianVariant::Updates) => {
- "http://deb.debian.org/debian buster-updates"
- }
- (Release::Buster, DebianVariant::Backports) => {
- "http://deb.debian.org/debian buster-backports"
- }
- (Release::Buster, DebianVariant::Debug) => {
- "http://deb.debian.org/debian-debug buster-debug"
- }
- };
-
- let url = format!("{url} {components}");
- let key = match (release, variant) {
- (Release::Bullseye, DebianVariant::Security) => {
- "/usr/share/keyrings/debian-archive-bullseye-security-automatic.gpg"
- }
- (Release::Bullseye, _) => {
- "/usr/share/keyrings/debian-archive-bullseye-automatic.gpg"
- }
- (Release::Buster, DebianVariant::Security) => {
- "/usr/share/keyrings/debian-archive-buster-security-automatic.gpg"
- }
- (Release::Buster, _) => "/usr/share/keyrings/debian-archive-buster-stable.gpg",
- };
-
- let suggested_id = format!("{dist}_{release}_{variant}");
-
- (url, key.to_string(), Some(suggested_id))
+ derive_debian_repo(release, variant, &components)
}
Distro::PveCeph => {
enum CephRelease {
);
let suggested_id = format!("ceph_{ceph_release}_{release}");
- (url, key.to_string(), Some(suggested_id))
+ (url, key.to_string(), suggested_id)
}
product => {
let variants = &[
let suggested_id = format!("{product}_{release}_{variant}");
- (url, key.to_string(), Some(suggested_id))
+ add_debian_repo = read_bool_from_tty(
+ "Should missing Debian mirrors for the selected product be auto-added",
+ Some(true),
+ )?;
+
+ (url, key.to_string(), suggested_id)
}
};
let architectures = vec!["amd64".to_string(), "all".to_string()];
- (format!("deb {url}"), key_path, architectures, suggested_id)
+
+ if add_debian_repo {
+ extra_repos.push(derive_debian_repo(
+ release,
+ &DebianVariant::Main,
+ "main contrib",
+ ));
+ extra_repos.push(derive_debian_repo(
+ release,
+ &DebianVariant::Updates,
+ "main contrib",
+ ));
+ extra_repos.push(derive_debian_repo(
+ release,
+ &DebianVariant::Security,
+ "main contrib",
+ ));
+ }
+ (
+ format!("deb {url}"),
+ key_path,
+ architectures,
+ Some(suggested_id),
+ )
} else {
let repo = read_string_from_tty("Enter repository line in sources.list format", None)?;
- let key_path = read_string_from_tty("Enter path to repository key file", None)?;
+ let key_path = read_string_from_tty("Enter (absolute) path to repository key file", None)?;
let architectures =
read_string_from_tty("Enter list of architectures to mirror", Some("amd64,all"))?;
let architectures: Vec<String> = architectures
break id;
};
- let dir = loop {
- let path =
- read_string_from_tty("Enter path where mirrored repository will be stored", None)?;
- if Path::new(&path).exists() {
- eprintln!("Path already exists.");
+ let base_dir = loop {
+ let path = read_string_from_tty(
+ "Enter (absolute) base path where mirrored repositories will be stored",
+ Some("/var/lib/proxmox-offline-mirror/mirrors/"),
+ )?;
+ if !path.starts_with('/') {
+ eprintln!("Path must start with '/'");
} else {
break path;
}
)?;
let sync = read_bool_from_tty("Should newly written files be written using FSYNC to ensure crash-consistency? (io-intensive!)", Some(true))?;
- Ok(MirrorConfig {
+ let mut configs = Vec::with_capacity(extra_repos.len() + 1);
+
+ for (url, key_path, suggested_id) in extra_repos {
+ if config.sections.contains_key(&suggested_id) {
+ eprintln!("config section '{suggested_id}' already exists, skipping..");
+ } else {
+ let repository = format!("deb {url}");
+
+ configs.push(MirrorConfig {
+ id: suggested_id,
+ repository,
+ architectures: architectures.clone(),
+ key_path,
+ verify,
+ sync,
+ base_dir: base_dir.clone(),
+ use_subscription: None,
+ ignore_errors: false,
+ });
+ }
+ }
+
+ let main_config = MirrorConfig {
id,
repository,
architectures,
key_path,
verify,
sync,
- dir,
+ base_dir,
use_subscription,
- })
+ ignore_errors: false,
+ };
+
+ configs.push(main_config);
+ Ok(configs)
}
fn action_add_medium(config: &SectionConfigData) -> Result<MediaConfig, Error> {
+ let id = loop {
+ let id = read_string_from_tty("Enter new medium ID", None)?;
+ if let Err(err) = MEDIA_ID_SCHEMA.parse_simple_value(&id) {
+ eprintln!("Not a valid medium ID: {err}");
+ continue;
+ }
+
+ if config.sections.contains_key(&id) {
+ eprintln!("Config entry '{id}' already exists!");
+ continue;
+ }
+
+ break id;
+ };
+
let mountpoint = loop {
- let path = read_string_from_tty("Enter path where medium is mounted", None)?;
+ let path = read_string_from_tty("Enter (absolute) path where medium is mounted", None)?;
+ if !path.starts_with('/') {
+ eprintln!("Path must start with '/'");
+ continue;
+ }
+
let mountpoint = Path::new(&path);
if !mountpoint.exists() {
eprintln!("Path doesn't exist.");
enum Action {
SelectMirror,
+ SelectAllMirrors,
DeselectMirror,
+ DeselectAllMirrors,
Proceed,
}
- let actions = &[
- (Action::SelectMirror, "Add mirror to selected mirrors."),
- (
- Action::DeselectMirror,
- "Remove mirror from selected mirrors.",
- ),
- (Action::Proceed, "Proceed"),
- ];
loop {
println!();
- if selected_mirrors.is_empty() {
- println!("No mirrors selected so far.");
+ let actions = if selected_mirrors.is_empty() {
+ println!("No mirrors selected for inclusion on medium so far.");
+ vec![
+ (Action::SelectMirror, "Add mirror to selection."),
+ (Action::SelectAllMirrors, "Add all mirrors to selection."),
+ (Action::Proceed, "Proceed"),
+ ]
} else {
- println!("Selected mirrors:");
+ println!("Mirrors selected for inclusion on medium:");
for id in &selected_mirrors {
println!("\t- {id}");
}
- }
+ println!();
+ if available_mirrors.is_empty() {
+ println!("No more mirrors available for selection!");
+ vec![
+ (Action::DeselectMirror, "Remove mirror from selection."),
+ (
+ Action::DeselectAllMirrors,
+ "Remove all mirrors from selection.",
+ ),
+ (Action::Proceed, "Proceed"),
+ ]
+ } else {
+ vec![
+ (Action::SelectMirror, "Add mirror to selection."),
+ (Action::SelectAllMirrors, "Add all mirrors to selection."),
+ (Action::DeselectMirror, "Remove mirror from selection."),
+ (
+ Action::DeselectAllMirrors,
+ "Remove all mirrors from selection.",
+ ),
+ (Action::Proceed, "Proceed"),
+ ]
+ }
+ };
+
println!();
- let action = read_selection_from_tty("Select action", actions, Some(0))?;
+ let action = read_selection_from_tty("Select action", &actions, Some(0))?;
println!();
match action {
Action::SelectMirror => {
if available_mirrors.is_empty() {
- println!("No unselected mirrors available.");
+ println!("No (more) unselected mirrors available.");
continue;
}
.collect();
selected_mirrors.push(selected);
}
+ Action::SelectAllMirrors => {
+ selected_mirrors.extend_from_slice(&available_mirrors);
+ available_mirrors.truncate(0);
+ }
Action::DeselectMirror => {
if selected_mirrors.is_empty() {
- println!("No selected mirrors available.");
+ println!("No mirrors selected (yet).");
continue;
}
.collect();
available_mirrors.push(selected);
}
+ Action::DeselectAllMirrors => {
+ available_mirrors.extend_from_slice(&selected_mirrors);
+ selected_mirrors.truncate(0);
+ }
Action::Proceed => {
break;
}
)?;
let sync = read_bool_from_tty("Should newly written files be written using FSYNC to ensure crash-consistency? (io-intensive!)", Some(true))?;
- let id = loop {
- let mut id = read_string_from_tty("Enter medium ID", None)?;
- while let Err(err) = MIRROR_ID_SCHEMA.parse_simple_value(&id) {
- eprintln!("Not a valid medium ID: {err}");
- id = read_string_from_tty("Enter medium ID", None)?;
- }
-
- if config.sections.contains_key(&id) {
- eprintln!("Config entry '{id}' already exists!");
- continue;
- }
-
- break id;
- };
-
Ok(MediaConfig {
id,
mountpoint,
#[api(
input: {
properties: {
+ config: {
+ type: String,
+ optional: true,
+ description: "Path to mirroring config file.",
+ },
},
},
)]
/// Interactive setup wizard.
-async fn setup(_param: Value) -> Result<(), Error> {
+async fn setup(config: Option<String>, _param: Value) -> Result<(), Error> {
if !tty::stdin_isatty() {
bail!("Setup wizard can only run interactively.");
}
- let config_file = read_string_from_tty("Mirror config file", Some(DEFAULT_CONFIG_PATH))?;
+ let config_file = config.unwrap_or_else(get_config_path);
+
let _lock = proxmox_offline_mirror::config::lock_config(&config_file)?;
let (mut config, _digest) = proxmox_offline_mirror::config::config(&config_file)?;
Quit,
}
- let actions = &[
- (Action::AddMirror, "Add new mirror entry"),
- (Action::AddMedium, "Add new medium entry"),
- (Action::Quit, "Quit"),
- ];
-
loop {
println!();
+ let mut mirror_defined = false;
if !config.sections.is_empty() {
println!("Existing config entries:");
for (section, (section_type, _)) in config.sections.iter() {
+ if section_type == "mirror" {
+ mirror_defined = true;
+ }
println!("{section_type} '{section}'");
}
println!();
}
- match read_selection_from_tty("Select Action:", actions, Some(0))? {
+ let actions = if mirror_defined {
+ vec![
+ (Action::AddMirror, "Add new mirror entry"),
+ (Action::AddMedium, "Add new medium entry"),
+ (Action::Quit, "Quit"),
+ ]
+ } else {
+ vec![
+ (Action::AddMirror, "Add new mirror entry"),
+ (Action::Quit, "Quit"),
+ ]
+ };
+
+ match read_selection_from_tty("Select Action:", &actions, Some(0))? {
Action::Quit => break,
Action::AddMirror => {
- let mirror_config = action_add_mirror(&config)?;
- let id = mirror_config.id.clone();
- mirror::init(&mirror_config)?;
- config.set_data(&id, "mirror", mirror_config)?;
- save_config(&config_file, &config)?;
- println!("Config entry '{id}' added");
- println!("Run \"proxmox-apt-mirror mirror snapshot create --config '{config_file}' --id '{id}'\" to create a new mirror snapshot.");
+ for mirror_config in action_add_mirror(&config)? {
+ let id = mirror_config.id.clone();
+ mirror::init(&mirror_config)?;
+ config.set_data(&id, "mirror", mirror_config)?;
+ save_config(&config_file, &config)?;
+ println!("Config entry '{id}' added");
+ println!("Run \"proxmox-offline-mirror mirror snapshot create --config '{config_file}' '{id}'\" to create a new mirror snapshot.");
+ }
}
Action::AddMedium => {
let media_config = action_add_medium(&config)?;
config.set_data(&id, "medium", media_config)?;
save_config(&config_file, &config)?;
println!("Config entry '{id}' added");
- println!("Run \"proxmox-apt-mirror medium sync --config '{config_file}' --id '{id}'\" to sync mirror snapshots to medium.");
+ println!("Run \"proxmox-offline-mirror medium sync --config '{config_file}' '{id}'\" to sync mirror snapshots to medium.");
}
}
}