-use apt_pkg_native::Cache;
-use anyhow::{Error, bail};
+use anyhow::{Error, bail, format_err};
use serde_json::{json, Value};
+use std::collections::HashMap;
+
+use proxmox::tools::fs::{replace_file, CreateOptions};
+use proxmox_router::{
+ list_subdirs_api_method, RpcEnvironment, RpcEnvironmentType, Permission, Router, SubdirMap
+};
+use proxmox_schema::api;
+
+use proxmox_apt::repositories::{
+ APTRepositoryFile, APTRepositoryFileError, APTRepositoryHandle, APTRepositoryInfo,
+ APTStandardRepository,
+};
+use proxmox_http::ProxyConfig;
+
+use pbs_api_types::{
+ APTUpdateInfo, NODE_SCHEMA, PROXMOX_CONFIG_DIGEST_SCHEMA, UPID_SCHEMA,
+ PRIV_SYS_AUDIT, PRIV_SYS_MODIFY,
+};
+
+use crate::config::node;
+use proxmox_rest_server::WorkerTask;
+use crate::tools::{
+ apt,
+ pbs_simple_http,
+ subscription,
+};
-use proxmox::{list_subdirs_api_method, const_regex};
-use proxmox::api::{api, RpcEnvironment, RpcEnvironmentType, Permission};
-use proxmox::api::router::{Router, SubdirMap};
+#[api(
+ input: {
+ properties: {
+ node: {
+ schema: NODE_SCHEMA,
+ },
+ },
+ },
+ returns: {
+ description: "A list of packages with available updates.",
+ type: Array,
+ items: {
+ type: APTUpdateInfo
+ },
+ },
+ protected: true,
+ access: {
+ permission: &Permission::Privilege(&[], PRIV_SYS_AUDIT, false),
+ },
+)]
+/// List available APT updates
+fn apt_update_available(_param: Value) -> Result<Value, Error> {
-use crate::server::WorkerTask;
+ if let Ok(false) = apt::pkg_cache_expired() {
+ if let Ok(Some(cache)) = apt::read_pkg_state() {
+ return Ok(json!(cache.package_status));
+ }
+ }
-use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
-use crate::api2::types::{APTUpdateInfo, NODE_SCHEMA, Userid, UPID_SCHEMA};
+ let cache = apt::update_cache()?;
-const_regex! {
- VERSION_EPOCH_REGEX = r"^\d+:";
- FILENAME_EXTRACT_REGEX = r"^.*/.*?_(.*)_Packages$";
+ Ok(json!(cache.package_status))
}
-// FIXME: Replace with call to 'apt changelog <pkg> --print-uris'. Currently
-// not possible as our packages do not have a URI set in their Release file
-fn get_changelog_url(
- package: &str,
- filename: &str,
- source_pkg: &str,
- version: &str,
- source_version: &str,
- origin: &str,
- component: &str,
-) -> Result<String, Error> {
- if origin == "" {
- bail!("no origin available for package {}", package);
+pub fn update_apt_proxy_config(proxy_config: Option<&ProxyConfig>) -> Result<(), Error> {
+
+ const PROXY_CFG_FN: &str = "/etc/apt/apt.conf.d/76pveproxy"; // use same file as PVE
+
+ if let Some(proxy_config) = proxy_config {
+ let proxy = proxy_config.to_proxy_string()?;
+ let data = format!("Acquire::http::Proxy \"{}\";\n", proxy);
+ replace_file(PROXY_CFG_FN, data.as_bytes(), CreateOptions::new(), false)
+ } else {
+ match std::fs::remove_file(PROXY_CFG_FN) {
+ Ok(()) => Ok(()),
+ Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()),
+ Err(err) => bail!("failed to remove proxy config '{}' - {}", PROXY_CFG_FN, err),
+ }
}
+}
- if origin == "Debian" {
- let source_version = (VERSION_EPOCH_REGEX.regex_obj)().replace_all(source_version, "");
+fn read_and_update_proxy_config() -> Result<Option<ProxyConfig>, Error> {
+ let proxy_config = if let Ok((node_config, _digest)) = node::config() {
+ node_config.http_proxy()
+ } else {
+ None
+ };
+ update_apt_proxy_config(proxy_config.as_ref())?;
- let prefix = if source_pkg.starts_with("lib") {
- source_pkg.get(0..4)
- } else {
- source_pkg.get(0..1)
- };
+ Ok(proxy_config)
+}
- let prefix = match prefix {
- Some(p) => p,
- None => bail!("cannot get starting characters of package name '{}'", package)
- };
+fn do_apt_update(worker: &WorkerTask, quiet: bool) -> Result<(), Error> {
+ if !quiet { worker.log_message("starting apt-get update") }
- // note: security updates seem to not always upload a changelog for
- // their package version, so this only works *most* of the time
- return Ok(format!("https://metadata.ftp-master.debian.org/changelogs/main/{}/{}/{}_{}_changelog",
- prefix, source_pkg, source_pkg, source_version));
+ read_and_update_proxy_config()?;
- } else if origin == "Proxmox" {
- let version = (VERSION_EPOCH_REGEX.regex_obj)().replace_all(version, "");
+ let mut command = std::process::Command::new("apt-get");
+ command.arg("update");
- let base = match (FILENAME_EXTRACT_REGEX.regex_obj)().captures(filename) {
- Some(captures) => {
- let base_capture = captures.get(1);
- match base_capture {
- Some(base_underscore) => base_underscore.as_str().replace("_", "/"),
- None => bail!("incompatible filename, cannot find regex group")
- }
- },
- None => bail!("incompatible filename, doesn't match regex")
- };
+ // apt "errors" quite easily, and run_command is a bit rigid, so handle this inline for now.
+ let output = command.output()
+ .map_err(|err| format_err!("failed to execute {:?} - {}", command, err))?;
- return Ok(format!("http://download.proxmox.com/{}/{}_{}.changelog",
- base, package, version));
+ if !quiet {
+ worker.log_message(String::from_utf8(output.stdout)?);
}
- bail!("unknown origin ({}) or component ({})", origin, component)
+ // TODO: improve run_command to allow outputting both, stderr and stdout
+ if !output.status.success() {
+ if output.status.code().is_some() {
+ let msg = String::from_utf8(output.stderr)
+ .map(|m| if m.is_empty() { String::from("no error message") } else { m })
+ .unwrap_or_else(|_| String::from("non utf8 error message (suppressed)"));
+ worker.log_warning(msg);
+ } else {
+ bail!("terminated by signal");
+ }
+ }
+ Ok(())
}
-struct FilterData<'a> {
- // this is version info returned by APT
- installed_version: &'a str,
- candidate_version: &'a str,
+#[api(
+ protected: true,
+ input: {
+ properties: {
+ node: {
+ schema: NODE_SCHEMA,
+ },
+ notify: {
+ type: bool,
+ description: r#"Send notification mail about new package updates available to the
+ email address configured for 'root@pam')."#,
+ default: false,
+ optional: true,
+ },
+ quiet: {
+ description: "Only produces output suitable for logging, omitting progress indicators.",
+ type: bool,
+ default: false,
+ optional: true,
+ },
+ },
+ },
+ returns: {
+ schema: UPID_SCHEMA,
+ },
+ access: {
+ permission: &Permission::Privilege(&[], PRIV_SYS_MODIFY, false),
+ },
+)]
+/// Update the APT database
+pub fn apt_update_database(
+ notify: bool,
+ quiet: bool,
+ rpcenv: &mut dyn RpcEnvironment,
+) -> Result<String, Error> {
+
+ let auth_id = rpcenv.get_auth_id().unwrap();
+ let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI;
+
+ let upid_str = WorkerTask::new_thread("aptupdate", None, auth_id, to_stdout, move |worker| {
+ do_apt_update(&worker, quiet)?;
- // this is the version info the filter is supposed to check
- active_version: &'a str,
+ let mut cache = apt::update_cache()?;
+
+ if notify {
+ let mut notified = match cache.notified {
+ Some(notified) => notified,
+ None => std::collections::HashMap::new(),
+ };
+ let mut to_notify: Vec<&APTUpdateInfo> = Vec::new();
+
+ for pkg in &cache.package_status {
+ match notified.insert(pkg.package.to_owned(), pkg.version.to_owned()) {
+ Some(notified_version) => {
+ if notified_version != pkg.version {
+ to_notify.push(pkg);
+ }
+ },
+ None => to_notify.push(pkg),
+ }
+ }
+ if !to_notify.is_empty() {
+ to_notify.sort_unstable_by_key(|k| &k.package);
+ crate::server::send_updates_available(&to_notify)?;
+ }
+ cache.notified = Some(notified);
+ apt::write_pkg_cache(&cache)?;
+ }
+
+ Ok(())
+ })?;
+
+ Ok(upid_str)
}
-fn list_installed_apt_packages<F: Fn(FilterData) -> bool>(filter: F)
- -> Vec<APTUpdateInfo> {
+#[api(
+ protected: true,
+ input: {
+ properties: {
+ node: {
+ schema: NODE_SCHEMA,
+ },
+ name: {
+ description: "Package name to get changelog of.",
+ type: String,
+ },
+ version: {
+ description: "Package version to get changelog of. Omit to use candidate version.",
+ type: String,
+ optional: true,
+ },
+ },
+ },
+ returns: {
+ schema: UPID_SCHEMA,
+ },
+ access: {
+ permission: &Permission::Privilege(&[], PRIV_SYS_MODIFY, false),
+ },
+)]
+/// Retrieve the changelog of the specified package.
+fn apt_get_changelog(
+ param: Value,
+) -> Result<Value, Error> {
+
+ let name = pbs_tools::json::required_string_param(¶m, "name")?.to_owned();
+ let version = param["version"].as_str();
+
+ let pkg_info = apt::list_installed_apt_packages(|data| {
+ match version {
+ Some(version) => version == data.active_version,
+ None => data.active_version == data.candidate_version
+ }
+ }, Some(&name));
- let mut ret = Vec::new();
+ if pkg_info.is_empty() {
+ bail!("Package '{}' not found", name);
+ }
- // note: this is not an 'apt update', it just re-reads the cache from disk
- let mut cache = Cache::get_singleton();
- cache.reload();
+ let proxy_config = read_and_update_proxy_config()?;
+ let mut client = pbs_simple_http(proxy_config);
- let mut cache_iter = cache.iter();
+ let changelog_url = &pkg_info[0].change_log_url;
+ // FIXME: use 'apt-get changelog' for proxmox packages as well, once repo supports it
+ if changelog_url.starts_with("http://download.proxmox.com/") {
+ let changelog = proxmox_async::runtime::block_on(client.get_string(changelog_url, None))
+ .map_err(|err| format_err!("Error downloading changelog from '{}': {}", changelog_url, err))?;
+ Ok(json!(changelog))
- loop {
- let view = match cache_iter.next() {
- Some(view) => view,
- None => break
+ } else if changelog_url.starts_with("https://enterprise.proxmox.com/") {
+ let sub = match subscription::read_subscription()? {
+ Some(sub) => sub,
+ None => bail!("cannot retrieve changelog from enterprise repo: no subscription info found")
+ };
+ let (key, id) = match sub.key {
+ Some(key) => {
+ match sub.serverid {
+ Some(id) => (key, id),
+ None =>
+ bail!("cannot retrieve changelog from enterprise repo: no server id found")
+ }
+ },
+ None => bail!("cannot retrieve changelog from enterprise repo: no subscription key found")
};
- let current_version = view.current_version();
- let candidate_version = view.candidate_version();
+ let mut auth_header = HashMap::new();
+ auth_header.insert("Authorization".to_owned(),
+ format!("Basic {}", base64::encode(format!("{}:{}", key, id))));
- let (current_version, candidate_version) = match (current_version, candidate_version) {
- (Some(cur), Some(can)) => (cur, can), // package installed and there is an update
- (Some(cur), None) => (cur.clone(), cur), // package installed and up-to-date
- (None, Some(_)) => continue, // package could be installed
- (None, None) => continue, // broken
- };
+ let changelog = proxmox_async::runtime::block_on(client.get_string(changelog_url, Some(&auth_header)))
+ .map_err(|err| format_err!("Error downloading changelog from '{}': {}", changelog_url, err))?;
+ Ok(json!(changelog))
- // get additional information via nested APT 'iterators'
- let mut view_iter = view.versions();
- while let Some(ver) = view_iter.next() {
-
- let package = view.name();
- let version = ver.version();
- let mut origin_res = "unknown".to_owned();
- let mut section_res = "unknown".to_owned();
- let mut priority_res = "unknown".to_owned();
- let mut change_log_url = "".to_owned();
- let mut short_desc = package.clone();
- let mut long_desc = "".to_owned();
-
- let fd = FilterData {
- installed_version: ¤t_version,
- candidate_version: &candidate_version,
- active_version: &version,
- };
+ } else {
+ let mut command = std::process::Command::new("apt-get");
+ command.arg("changelog");
+ command.arg("-qq"); // don't display download progress
+ command.arg(name);
+ let output = pbs_tools::run_command(command, None)?;
+ Ok(json!(output))
+ }
+}
- if filter(fd) {
- if let Some(section) = ver.section() {
- section_res = section;
- }
+#[api(
+ input: {
+ properties: {
+ node: {
+ schema: NODE_SCHEMA,
+ },
+ },
+ },
+ returns: {
+ description: "List of more relevant packages.",
+ type: Array,
+ items: {
+ type: APTUpdateInfo,
+ },
+ },
+ access: {
+ permission: &Permission::Privilege(&[], PRIV_SYS_AUDIT, false),
+ },
+)]
+/// Get package information for important Proxmox Backup Server packages.
+pub fn get_versions() -> Result<Vec<APTUpdateInfo>, Error> {
+ const PACKAGES: &[&str] = &[
+ "ifupdown2",
+ "libjs-extjs",
+ "proxmox-backup",
+ "proxmox-backup-docs",
+ "proxmox-backup-client",
+ "proxmox-backup-server",
+ "proxmox-mini-journalreader",
+ "proxmox-widget-toolkit",
+ "pve-xtermjs",
+ "smartmontools",
+ "zfsutils-linux",
+ ];
+
+ fn unknown_package(package: String, extra_info: Option<String>) -> APTUpdateInfo {
+ APTUpdateInfo {
+ package,
+ title: "unknown".into(),
+ arch: "unknown".into(),
+ description: "unknown".into(),
+ version: "unknown".into(),
+ old_version: "unknown".into(),
+ origin: "unknown".into(),
+ priority: "unknown".into(),
+ section: "unknown".into(),
+ change_log_url: "unknown".into(),
+ extra_info,
+ }
+ }
- if let Some(prio) = ver.priority_type() {
- priority_res = prio;
- }
+ let is_kernel = |name: &str| name.starts_with("pve-kernel-");
- // assume every package has only one origin file (not
- // origin, but origin *file*, for some reason those seem to
- // be different concepts in APT)
- let mut origin_iter = ver.origin_iter();
- let origin = origin_iter.next();
- if let Some(origin) = origin {
-
- if let Some(sd) = origin.short_desc() {
- short_desc = sd;
- }
-
- if let Some(ld) = origin.long_desc() {
- long_desc = ld;
- }
-
- // the package files appear in priority order, meaning
- // the one for the candidate version is first - this is fine
- // however, as the source package should be the same for all
- // versions anyway
- let mut pkg_iter = origin.file();
- let pkg_file = pkg_iter.next();
- if let Some(pkg_file) = pkg_file {
- if let Some(origin_name) = pkg_file.origin() {
- origin_res = origin_name;
- }
+ let mut packages: Vec<APTUpdateInfo> = Vec::new();
+ let pbs_packages = apt::list_installed_apt_packages(
+ |filter| {
+ filter.installed_version == Some(filter.active_version)
+ && (is_kernel(filter.package) || PACKAGES.contains(&filter.package))
+ },
+ None,
+ );
- let filename = pkg_file.file_name();
- let source_pkg = ver.source_package();
- let source_ver = ver.source_version();
- let component = pkg_file.component();
-
- // build changelog URL from gathered information
- // ignore errors, use empty changelog instead
- let url = get_changelog_url(&package, &filename, &source_pkg,
- &version, &source_ver, &origin_res, &component);
- if let Ok(url) = url {
- change_log_url = url;
- }
- }
- }
+ let running_kernel = format!(
+ "running kernel: {}",
+ nix::sys::utsname::uname().release().to_owned()
+ );
+ if let Some(proxmox_backup) = pbs_packages.iter().find(|pkg| pkg.package == "proxmox-backup") {
+ let mut proxmox_backup = proxmox_backup.clone();
+ proxmox_backup.extra_info = Some(running_kernel);
+ packages.push(proxmox_backup);
+ } else {
+ packages.push(unknown_package("proxmox-backup".into(), Some(running_kernel)));
+ }
- let info = APTUpdateInfo {
- package,
- title: short_desc,
- arch: view.arch(),
- description: long_desc,
- change_log_url,
- origin: origin_res,
- version: candidate_version.clone(),
- old_version: current_version.clone(),
- priority: priority_res,
- section: section_res,
- };
- ret.push(info);
- }
+ let version = pbs_buildcfg::PROXMOX_PKG_VERSION;
+ let release = pbs_buildcfg::PROXMOX_PKG_RELEASE;
+ let daemon_version_info = Some(format!("running version: {}.{}", version, release));
+ if let Some(pkg) = pbs_packages.iter().find(|pkg| pkg.package == "proxmox-backup-server") {
+ let mut pkg = pkg.clone();
+ pkg.extra_info = daemon_version_info;
+ packages.push(pkg);
+ } else {
+ packages.push(unknown_package("proxmox-backup".into(), daemon_version_info));
+ }
+
+ let mut kernel_pkgs: Vec<APTUpdateInfo> = pbs_packages
+ .iter()
+ .filter(|pkg| is_kernel(&pkg.package))
+ .cloned()
+ .collect();
+ // make sure the cache mutex gets dropped before the next call to list_installed_apt_packages
+ {
+ let cache = apt_pkg_native::Cache::get_singleton();
+ kernel_pkgs.sort_by(|left, right| {
+ cache
+ .compare_versions(&left.old_version, &right.old_version)
+ .reverse()
+ });
+ }
+ packages.append(&mut kernel_pkgs);
+
+ // add entry for all packages we're interested in, even if not installed
+ for pkg in PACKAGES.iter() {
+ if pkg == &"proxmox-backup" || pkg == &"proxmox-backup-server" {
+ continue;
+ }
+ match pbs_packages.iter().find(|item| &item.package == pkg) {
+ Some(apt_pkg) => packages.push(apt_pkg.to_owned()),
+ None => packages.push(unknown_package(pkg.to_string(), None)),
}
}
- return ret;
+ Ok(packages)
}
#[api(
},
},
returns: {
- description: "A list of packages with available updates.",
- type: Array,
- items: { type: APTUpdateInfo },
+ type: Object,
+ description: "Result from parsing the APT repository files in /etc/apt/.",
+ properties: {
+ files: {
+ description: "List of parsed repository files.",
+ type: Array,
+ items: {
+ type: APTRepositoryFile,
+ },
+ },
+ errors: {
+ description: "List of problematic files.",
+ type: Array,
+ items: {
+ type: APTRepositoryFileError,
+ },
+ },
+ digest: {
+ schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
+ },
+ infos: {
+ description: "List of additional information/warnings about the repositories.",
+ items: {
+ type: APTRepositoryInfo,
+ },
+ },
+ "standard-repos": {
+ description: "List of standard repositories and their configuration status.",
+ items: {
+ type: APTStandardRepository,
+ },
+ },
+ },
},
access: {
permission: &Permission::Privilege(&[], PRIV_SYS_AUDIT, false),
},
)]
-/// List available APT updates
-fn apt_update_available(_param: Value) -> Result<Value, Error> {
- let all_upgradeable = list_installed_apt_packages(|data|
- data.candidate_version == data.active_version &&
- data.installed_version != data.candidate_version
- );
- Ok(json!(all_upgradeable))
+/// Get APT repository information.
+pub fn get_repositories() -> Result<Value, Error> {
+ let (files, errors, digest) = proxmox_apt::repositories::repositories()?;
+ let digest = proxmox::tools::digest_to_hex(&digest);
+
+ let suite = proxmox_apt::repositories::get_current_release_codename()?;
+
+ let infos = proxmox_apt::repositories::check_repositories(&files, suite);
+ let standard_repos = proxmox_apt::repositories::standard_repositories(&files, "pbs", suite);
+
+ Ok(json!({
+ "files": files,
+ "errors": errors,
+ "digest": digest,
+ "infos": infos,
+ "standard-repos": standard_repos,
+ }))
}
#[api(
- protected: true,
input: {
properties: {
node: {
schema: NODE_SCHEMA,
},
- quiet: {
- description: "Only produces output suitable for logging, omitting progress indicators.",
- type: bool,
- default: false,
+ handle: {
+ type: APTRepositoryHandle,
+ },
+ digest: {
+ schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
optional: true,
},
},
},
- returns: {
- schema: UPID_SCHEMA,
- },
+ protected: true,
access: {
permission: &Permission::Privilege(&[], PRIV_SYS_MODIFY, false),
},
)]
-/// Update the APT database
-pub fn apt_update_database(
- quiet: Option<bool>,
- rpcenv: &mut dyn RpcEnvironment,
-) -> Result<String, Error> {
+/// Add the repository identified by the `handle`.
+/// If the repository is already configured, it will be set to enabled.
+///
+/// The `digest` parameter asserts that the configuration has not been modified.
+pub fn add_repository(handle: APTRepositoryHandle, digest: Option<String>) -> Result<(), Error> {
+ let (mut files, errors, current_digest) = proxmox_apt::repositories::repositories()?;
+
+ let suite = proxmox_apt::repositories::get_current_release_codename()?;
+
+ if let Some(expected_digest) = digest {
+ let current_digest = proxmox::tools::digest_to_hex(¤t_digest);
+ crate::tools::assert_if_modified(&expected_digest, ¤t_digest)?;
+ }
- let userid: Userid = rpcenv.get_user().unwrap().parse()?;
- let to_stdout = if rpcenv.env_type() == RpcEnvironmentType::CLI { true } else { false };
- let quiet = quiet.unwrap_or(API_METHOD_APT_UPDATE_DATABASE_PARAM_DEFAULT_QUIET);
+ // check if it's already configured first
+ for file in files.iter_mut() {
+ for repo in file.repositories.iter_mut() {
+ if repo.is_referenced_repository(handle, "pbs", &suite.to_string()) {
+ if repo.enabled {
+ return Ok(());
+ }
- let upid_str = WorkerTask::new_thread("aptupdate", None, userid, to_stdout, move |worker| {
- if !quiet { worker.log("starting apt-get update") }
+ repo.set_enabled(true);
+ file.write()?;
- // TODO: set proxy /etc/apt/apt.conf.d/76pbsproxy like PVE
+ return Ok(());
+ }
+ }
+ }
- let mut command = std::process::Command::new("apt-get");
- command.arg("update");
+ let (repo, path) = proxmox_apt::repositories::get_standard_repository(handle, "pbs", suite);
- let output = crate::tools::run_command(command, None)?;
- if !quiet { worker.log(output) }
+ if let Some(error) = errors.iter().find(|error| error.path == path) {
+ bail!(
+ "unable to parse existing file {} - {}",
+ error.path,
+ error.error,
+ );
+ }
- // TODO: add mail notify for new updates like PVE
+ if let Some(file) = files.iter_mut().find(|file| file.path == path) {
+ file.repositories.push(repo);
- Ok(())
- })?;
+ file.write()?;
+ } else {
+ let mut file = match APTRepositoryFile::new(&path)? {
+ Some(file) => file,
+ None => bail!("invalid path - {}", path),
+ };
- Ok(upid_str)
+ file.repositories.push(repo);
+
+ file.write()?;
+ }
+
+ Ok(())
+}
+
+#[api(
+ input: {
+ properties: {
+ node: {
+ schema: NODE_SCHEMA,
+ },
+ path: {
+ description: "Path to the containing file.",
+ type: String,
+ },
+ index: {
+ description: "Index within the file (starting from 0).",
+ type: usize,
+ },
+ enabled: {
+ description: "Whether the repository should be enabled or not.",
+ type: bool,
+ optional: true,
+ },
+ digest: {
+ schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
+ optional: true,
+ },
+ },
+ },
+ protected: true,
+ access: {
+ permission: &Permission::Privilege(&[], PRIV_SYS_MODIFY, false),
+ },
+)]
+/// Change the properties of the specified repository.
+///
+/// The `digest` parameter asserts that the configuration has not been modified.
+pub fn change_repository(
+ path: String,
+ index: usize,
+ enabled: Option<bool>,
+ digest: Option<String>,
+) -> Result<(), Error> {
+ let (mut files, errors, current_digest) = proxmox_apt::repositories::repositories()?;
+
+ if let Some(expected_digest) = digest {
+ let current_digest = proxmox::tools::digest_to_hex(¤t_digest);
+ crate::tools::assert_if_modified(&expected_digest, ¤t_digest)?;
+ }
+
+ if let Some(error) = errors.iter().find(|error| error.path == path) {
+ bail!("unable to parse file {} - {}", error.path, error.error);
+ }
+
+ if let Some(file) = files.iter_mut().find(|file| file.path == path) {
+ if let Some(repo) = file.repositories.get_mut(index) {
+ if let Some(enabled) = enabled {
+ repo.set_enabled(enabled);
+ }
+
+ file.write()?;
+ } else {
+ bail!("invalid index - {}", index);
+ }
+ } else {
+ bail!("invalid path - {}", path);
+ }
+
+ Ok(())
}
const SUBDIRS: SubdirMap = &[
+ ("changelog", &Router::new().get(&API_METHOD_APT_GET_CHANGELOG)),
+ ("repositories", &Router::new()
+ .get(&API_METHOD_GET_REPOSITORIES)
+ .post(&API_METHOD_CHANGE_REPOSITORY)
+ .put(&API_METHOD_ADD_REPOSITORY)
+ ),
("update", &Router::new()
.get(&API_METHOD_APT_UPDATE_AVAILABLE)
.post(&API_METHOD_APT_UPDATE_DATABASE)
),
+ ("versions", &Router::new().get(&API_METHOD_GET_VERSIONS)),
];
pub const ROUTER: Router = Router::new()