1 use anyhow
::{Error, bail, format_err}
;
2 use serde_json
::{json, Value}
;
3 use std
::collections
::HashMap
;
5 use proxmox
::list_subdirs_api_method
;
6 use proxmox
::api
::{api, RpcEnvironment, RpcEnvironmentType, Permission}
;
7 use proxmox
::api
::router
::{Router, SubdirMap}
;
9 use crate::server
::WorkerTask
;
10 use crate::tools
::{apt, http, subscription}
;
12 use crate::config
::acl
::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}
;
13 use crate::api2
::types
::{Authid, APTUpdateInfo, NODE_SCHEMA, UPID_SCHEMA}
;
24 description
: "A list of packages with available updates.",
32 permission
: &Permission
::Privilege(&[], PRIV_SYS_AUDIT
, false),
35 /// List available APT updates
36 fn apt_update_available(_param
: Value
) -> Result
<Value
, Error
> {
38 if let Ok(false) = apt
::pkg_cache_expired() {
39 if let Ok(Some(cache
)) = apt
::read_pkg_state() {
40 return Ok(json
!(cache
.package_status
));
44 let cache
= apt
::update_cache()?
;
46 Ok(json
!(cache
.package_status
))
49 fn do_apt_update(worker
: &WorkerTask
, quiet
: bool
) -> Result
<(), Error
> {
50 if !quiet { worker.log("starting apt-get update") }
52 // TODO: set proxy /etc/apt/apt.conf.d/76pbsproxy like PVE
54 let mut command
= std
::process
::Command
::new("apt-get");
55 command
.arg("update");
57 // apt "errors" quite easily, and run_command is a bit rigid, so handle this inline for now.
58 let output
= command
.output()
59 .map_err(|err
| format_err
!("failed to execute {:?} - {}", command
, err
))?
;
62 worker
.log(String
::from_utf8(output
.stdout
)?
);
65 // TODO: improve run_command to allow outputting both, stderr and stdout
66 if !output
.status
.success() {
67 if output
.status
.code().is_some() {
68 let msg
= String
::from_utf8(output
.stderr
)
69 .map(|m
| if m
.is_empty() { String::from("no error message") }
else { m }
)
70 .unwrap_or_else(|_
| String
::from("non utf8 error message (suppressed)"));
73 bail
!("terminated by signal");
88 description
: r
#"Send notification mail about new package updates available to the
89 email address configured for 'root@pam')."#,
94 description
: "Only produces output suitable for logging, omitting progress indicators.",
105 permission
: &Permission
::Privilege(&[], PRIV_SYS_MODIFY
, false),
108 /// Update the APT database
109 pub fn apt_update_database(
112 rpcenv
: &mut dyn RpcEnvironment
,
113 ) -> Result
<String
, Error
> {
115 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
116 let to_stdout
= rpcenv
.env_type() == RpcEnvironmentType
::CLI
;
118 let upid_str
= WorkerTask
::new_thread("aptupdate", None
, auth_id
, to_stdout
, move |worker
| {
119 do_apt_update(&worker
, quiet
)?
;
121 let mut cache
= apt
::update_cache()?
;
124 let mut notified
= match cache
.notified
{
125 Some(notified
) => notified
,
126 None
=> std
::collections
::HashMap
::new(),
128 let mut to_notify
: Vec
<&APTUpdateInfo
> = Vec
::new();
130 for pkg
in &cache
.package_status
{
131 match notified
.insert(pkg
.package
.to_owned(), pkg
.version
.to_owned()) {
132 Some(notified_version
) => {
133 if notified_version
!= pkg
.version
{
137 None
=> to_notify
.push(pkg
),
140 if !to_notify
.is_empty() {
141 to_notify
.sort_unstable_by_key(|k
| &k
.package
);
142 crate::server
::send_updates_available(&to_notify
)?
;
144 cache
.notified
= Some(notified
);
145 apt
::write_pkg_cache(&cache
)?
;
161 description
: "Package name to get changelog of.",
165 description
: "Package version to get changelog of. Omit to use candidate version.",
175 permission
: &Permission
::Privilege(&[], PRIV_SYS_MODIFY
, false),
178 /// Retrieve the changelog of the specified package.
179 fn apt_get_changelog(
181 ) -> Result
<Value
, Error
> {
183 let name
= crate::tools
::required_string_param(¶m
, "name")?
.to_owned();
184 let version
= param
["version"].as_str();
186 let pkg_info
= apt
::list_installed_apt_packages(|data
| {
188 Some(version
) => version
== data
.active_version
,
189 None
=> data
.active_version
== data
.candidate_version
193 if pkg_info
.is_empty() {
194 bail
!("Package '{}' not found", name
);
197 let changelog_url
= &pkg_info
[0].change_log_url
;
198 // FIXME: use 'apt-get changelog' for proxmox packages as well, once repo supports it
199 if changelog_url
.starts_with("http://download.proxmox.com/") {
200 let changelog
= crate::tools
::runtime
::block_on(http
::get_string(changelog_url
, None
))
201 .map_err(|err
| format_err
!("Error downloading changelog from '{}': {}", changelog_url
, err
))?
;
204 } else if changelog_url
.starts_with("https://enterprise.proxmox.com/") {
205 let sub
= match subscription
::read_subscription()?
{
207 None
=> bail
!("cannot retrieve changelog from enterprise repo: no subscription info found")
209 let (key
, id
) = match sub
.key
{
212 Some(id
) => (key
, id
),
214 bail
!("cannot retrieve changelog from enterprise repo: no server id found")
217 None
=> bail
!("cannot retrieve changelog from enterprise repo: no subscription key found")
220 let mut auth_header
= HashMap
::new();
221 auth_header
.insert("Authorization".to_owned(),
222 format
!("Basic {}", base64
::encode(format
!("{}:{}", key
, id
))));
224 let changelog
= crate::tools
::runtime
::block_on(http
::get_string(changelog_url
, Some(&auth_header
)))
225 .map_err(|err
| format_err
!("Error downloading changelog from '{}': {}", changelog_url
, err
))?
;
229 let mut command
= std
::process
::Command
::new("apt-get");
230 command
.arg("changelog");
231 command
.arg("-qq"); // don't display download progress
233 let output
= crate::tools
::run_command(command
, None
)?
;
247 description
: "List of more relevant packages.",
254 permission
: &Permission
::Privilege(&[], PRIV_SYS_AUDIT
, false),
257 /// Get package information for important Proxmox Backup Server packages.
258 pub fn get_versions() -> Result
<Vec
<APTUpdateInfo
>, Error
> {
259 const PACKAGES
: &[&str] = &[
263 "proxmox-backup-docs",
264 "proxmox-backup-client",
265 "proxmox-backup-server",
266 "proxmox-mini-journalreader",
267 "proxmox-widget-toolkit",
273 fn unknown_package(package
: String
, extra_info
: Option
<String
>) -> APTUpdateInfo
{
276 title
: "unknown".into(),
277 arch
: "unknown".into(),
278 description
: "unknown".into(),
279 version
: "unknown".into(),
280 old_version
: "unknown".into(),
281 origin
: "unknown".into(),
282 priority
: "unknown".into(),
283 section
: "unknown".into(),
284 change_log_url
: "unknown".into(),
289 let is_kernel
= |name
: &str| name
.starts_with("pve-kernel-");
291 let mut packages
: Vec
<APTUpdateInfo
> = Vec
::new();
292 let pbs_packages
= apt
::list_installed_apt_packages(
294 filter
.installed_version
== Some(filter
.active_version
)
295 && (is_kernel(filter
.package
) || PACKAGES
.contains(&filter
.package
))
300 let running_kernel
= format
!(
301 "running kernel: {}",
302 nix
::sys
::utsname
::uname().release().to_owned()
304 if let Some(proxmox_backup
) = pbs_packages
.iter().find(|pkg
| pkg
.package
== "proxmox-backup") {
305 let mut proxmox_backup
= proxmox_backup
.clone();
306 proxmox_backup
.extra_info
= Some(running_kernel
);
307 packages
.push(proxmox_backup
);
309 packages
.push(unknown_package("proxmox-backup".into(), Some(running_kernel
)));
312 let version
= crate::api2
::version
::PROXMOX_PKG_VERSION
;
313 let release
= crate::api2
::version
::PROXMOX_PKG_RELEASE
;
314 let daemon_version_info
= Some(format
!("running version: {}.{}", version
, release
));
315 if let Some(pkg
) = pbs_packages
.iter().find(|pkg
| pkg
.package
== "proxmox-backup-server") {
316 let mut pkg
= pkg
.clone();
317 pkg
.extra_info
= daemon_version_info
;
320 packages
.push(unknown_package("proxmox-backup".into(), daemon_version_info
));
323 let mut kernel_pkgs
: Vec
<APTUpdateInfo
> = pbs_packages
325 .filter(|pkg
| is_kernel(&pkg
.package
))
328 // make sure the cache mutex gets dropped before the next call to list_installed_apt_packages
330 let cache
= apt_pkg_native
::Cache
::get_singleton();
331 kernel_pkgs
.sort_by(|left
, right
| {
333 .compare_versions(&left
.old_version
, &right
.old_version
)
337 packages
.append(&mut kernel_pkgs
);
339 // add entry for all packages we're interested in, even if not installed
340 for pkg
in PACKAGES
.iter() {
341 if pkg
== &"proxmox-backup" || pkg
== &"proxmox-backup-server" {
344 match pbs_packages
.iter().find(|item
| &item
.package
== pkg
) {
345 Some(apt_pkg
) => packages
.push(apt_pkg
.to_owned()),
346 None
=> packages
.push(unknown_package(pkg
.to_string(), None
)),
353 const SUBDIRS
: SubdirMap
= &[
354 ("changelog", &Router
::new().get(&API_METHOD_APT_GET_CHANGELOG
)),
355 ("update", &Router
::new()
356 .get(&API_METHOD_APT_UPDATE_AVAILABLE
)
357 .post(&API_METHOD_APT_UPDATE_DATABASE
)
359 ("versions", &Router
::new().get(&API_METHOD_GET_VERSIONS
)),
362 pub const ROUTER
: Router
= Router
::new()
363 .get(&list_subdirs_api_method
!(SUBDIRS
))