use anyhow::{Error, bail, format_err};
use serde_json::{json, Value};
+use std::collections::HashMap;
use proxmox::list_subdirs_api_method;
use proxmox::api::{api, RpcEnvironment, RpcEnvironmentType, Permission};
use proxmox::api::router::{Router, SubdirMap};
use crate::server::WorkerTask;
-use crate::tools::{apt, http};
+use crate::tools::{apt, http, subscription};
use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
use crate::api2::types::{Authid, APTUpdateInfo, NODE_SCHEMA, UPID_SCHEMA};
/// List available APT updates
fn apt_update_available(_param: Value) -> Result<Value, Error> {
- match apt::pkg_cache_expired() {
- Ok(false) => {
- if let Ok(Some(cache)) = apt::read_pkg_state() {
- return Ok(json!(cache.package_status));
- }
- },
- _ => (),
+ if let Ok(false) = apt::pkg_cache_expired() {
+ if let Ok(Some(cache)) = apt::read_pkg_state() {
+ return Ok(json!(cache.package_status));
+ }
}
let cache = apt::update_cache()?;
- return Ok(json!(cache.package_status));
+ Ok(json!(cache.package_status))
}
fn do_apt_update(worker: &WorkerTask, quiet: bool) -> Result<(), Error> {
},
notify: {
type: bool,
- description: r#"Send notification mail about new package updates availanle to the
+ description: r#"Send notification mail about new package updates available to the
email address configured for 'root@pam')."#,
- optional: true,
default: false,
+ optional: true,
},
quiet: {
description: "Only produces output suitable for logging, omitting progress indicators.",
)]
/// Update the APT database
pub fn apt_update_database(
- notify: Option<bool>,
- quiet: Option<bool>,
+ notify: bool,
+ quiet: bool,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<String, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
- let to_stdout = if rpcenv.env_type() == RpcEnvironmentType::CLI { true } else { false };
- // FIXME: change to non-option in signature and drop below once we have proxmox-api-macro 0.2.3
- let quiet = quiet.unwrap_or(API_METHOD_APT_UPDATE_DATABASE_PARAM_DEFAULT_QUIET);
- let notify = notify.unwrap_or(API_METHOD_APT_UPDATE_DATABASE_PARAM_DEFAULT_NOTIFY);
+ 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)?;
}
}
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);
}
}, Some(&name));
- if pkg_info.len() == 0 {
+ if pkg_info.is_empty() {
bail!("Package '{}' not found", name);
}
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 = crate::tools::runtime::block_on(http::get_string(changelog_url))
+ let changelog = crate::tools::runtime::block_on(http::get_string(changelog_url, None))
.map_err(|err| format_err!("Error downloading changelog from '{}': {}", changelog_url, err))?;
- return Ok(json!(changelog));
+ Ok(json!(changelog))
+
+ } 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 mut auth_header = HashMap::new();
+ auth_header.insert("Authorization".to_owned(),
+ format!("Basic {}", base64::encode(format!("{}:{}", key, id))));
+
+ let changelog = crate::tools::runtime::block_on(http::get_string(changelog_url, Some(&auth_header)))
+ .map_err(|err| format_err!("Error downloading changelog from '{}': {}", changelog_url, err))?;
+ Ok(json!(changelog))
+
} 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 = crate::tools::run_command(command, None)?;
- return Ok(json!(output));
+ Ok(json!(output))
+ }
+}
+
+#[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,
+ }
}
+
+ let is_kernel = |name: &str| name.starts_with("pve-kernel-");
+
+ 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 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 version = crate::api2::version::PROXMOX_PKG_VERSION;
+ let release = crate::api2::version::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)),
+ }
+ }
+
+ Ok(packages)
}
const SUBDIRS: SubdirMap = &[
.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()