1 use apt_pkg_native
::Cache
;
2 use anyhow
::{Error, bail}
;
3 use serde_json
::{json, Value}
;
5 use proxmox
::{list_subdirs_api_method, const_regex}
;
6 use proxmox
::api
::{api, RpcEnvironment, RpcEnvironmentType, Permission}
;
7 use proxmox
::api
::router
::{Router, SubdirMap}
;
9 use crate::server
::WorkerTask
;
11 use crate::config
::acl
::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}
;
12 use crate::api2
::types
::{APTUpdateInfo, NODE_SCHEMA, Userid, UPID_SCHEMA}
;
15 VERSION_EPOCH_REGEX
= r
"^\d+:";
16 FILENAME_EXTRACT_REGEX
= r
"^.*/.*?_(.*)_Packages$";
19 // FIXME: Replace with call to 'apt changelog <pkg> --print-uris'. Currently
20 // not possible as our packages do not have a URI set in their Release file
29 ) -> Result
<String
, Error
> {
31 bail
!("no origin available for package {}", package
);
34 if origin
== "Debian" {
35 let source_version
= (VERSION_EPOCH_REGEX
.regex_obj
)().replace_all(source_version
, "");
37 let prefix
= if source_pkg
.starts_with("lib") {
43 let prefix
= match prefix
{
45 None
=> bail
!("cannot get starting characters of package name '{}'", package
)
48 // note: security updates seem to not always upload a changelog for
49 // their package version, so this only works *most* of the time
50 return Ok(format
!("https://metadata.ftp-master.debian.org/changelogs/main/{}/{}/{}_{}_changelog",
51 prefix
, source_pkg
, source_pkg
, source_version
));
53 } else if origin
== "Proxmox" {
54 let version
= (VERSION_EPOCH_REGEX
.regex_obj
)().replace_all(version
, "");
56 let base
= match (FILENAME_EXTRACT_REGEX
.regex_obj
)().captures(filename
) {
58 let base_capture
= captures
.get(1);
60 Some(base_underscore
) => base_underscore
.as_str().replace("_", "/"),
61 None
=> bail
!("incompatible filename, cannot find regex group")
64 None
=> bail
!("incompatible filename, doesn't match regex")
67 return Ok(format
!("http://download.proxmox.com/{}/{}_{}.changelog",
68 base
, package
, version
));
71 bail
!("unknown origin ({}) or component ({})", origin
, component
)
74 struct FilterData
<'a
> {
75 // this is version info returned by APT
76 installed_version
: &'a
str,
77 candidate_version
: &'a
str,
79 // this is the version info the filter is supposed to check
80 active_version
: &'a
str,
83 fn list_installed_apt_packages
<F
: Fn(FilterData
) -> bool
>(filter
: F
)
84 -> Vec
<APTUpdateInfo
> {
86 let mut ret
= Vec
::new();
88 // note: this is not an 'apt update', it just re-reads the cache from disk
89 let mut cache
= Cache
::get_singleton();
92 let mut cache_iter
= cache
.iter();
95 let view
= match cache_iter
.next() {
100 let current_version
= view
.current_version();
101 let candidate_version
= view
.candidate_version();
103 let (current_version
, candidate_version
) = match (current_version
, candidate_version
) {
104 (Some(cur
), Some(can
)) => (cur
, can
), // package installed and there is an update
105 (Some(cur
), None
) => (cur
.clone(), cur
), // package installed and up-to-date
106 (None
, Some(_
)) => continue, // package could be installed
107 (None
, None
) => continue, // broken
110 // get additional information via nested APT 'iterators'
111 let mut view_iter
= view
.versions();
112 while let Some(ver
) = view_iter
.next() {
114 let package
= view
.name();
115 let version
= ver
.version();
116 let mut origin_res
= "unknown".to_owned();
117 let mut section_res
= "unknown".to_owned();
118 let mut priority_res
= "unknown".to_owned();
119 let mut change_log_url
= "".to_owned();
120 let mut short_desc
= package
.clone();
121 let mut long_desc
= "".to_owned();
123 let fd
= FilterData
{
124 installed_version
: ¤t_version
,
125 candidate_version
: &candidate_version
,
126 active_version
: &version
,
130 if let Some(section
) = ver
.section() {
131 section_res
= section
;
134 if let Some(prio
) = ver
.priority_type() {
138 // assume every package has only one origin file (not
139 // origin, but origin *file*, for some reason those seem to
140 // be different concepts in APT)
141 let mut origin_iter
= ver
.origin_iter();
142 let origin
= origin_iter
.next();
143 if let Some(origin
) = origin
{
145 if let Some(sd
) = origin
.short_desc() {
149 if let Some(ld
) = origin
.long_desc() {
153 // the package files appear in priority order, meaning
154 // the one for the candidate version is first - this is fine
155 // however, as the source package should be the same for all
157 let mut pkg_iter
= origin
.file();
158 let pkg_file
= pkg_iter
.next();
159 if let Some(pkg_file
) = pkg_file
{
160 if let Some(origin_name
) = pkg_file
.origin() {
161 origin_res
= origin_name
;
164 let filename
= pkg_file
.file_name();
165 let source_pkg
= ver
.source_package();
166 let source_ver
= ver
.source_version();
167 let component
= pkg_file
.component();
169 // build changelog URL from gathered information
170 // ignore errors, use empty changelog instead
171 let url
= get_changelog_url(&package
, &filename
, &source_pkg
,
172 &version
, &source_ver
, &origin_res
, &component
);
173 if let Ok(url
) = url
{
174 change_log_url
= url
;
179 let info
= APTUpdateInfo
{
183 description
: long_desc
,
186 version
: candidate_version
.clone(),
187 old_version
: current_version
.clone(),
188 priority
: priority_res
,
189 section
: section_res
,
208 description
: "A list of packages with available updates.",
210 items
: { type: APTUpdateInfo }
,
213 permission
: &Permission
::Privilege(&[], PRIV_SYS_AUDIT
, false),
216 /// List available APT updates
217 fn apt_update_available(_param
: Value
) -> Result
<Value
, Error
> {
218 let all_upgradeable
= list_installed_apt_packages(|data
|
219 data
.candidate_version
== data
.active_version
&&
220 data
.installed_version
!= data
.candidate_version
222 Ok(json
!(all_upgradeable
))
233 description
: "Only produces output suitable for logging, omitting progress indicators.",
244 permission
: &Permission
::Privilege(&[], PRIV_SYS_MODIFY
, false),
247 /// Update the APT database
248 pub fn apt_update_database(
250 rpcenv
: &mut dyn RpcEnvironment
,
251 ) -> Result
<String
, Error
> {
253 let userid
: Userid
= rpcenv
.get_user().unwrap().parse()?
;
254 let to_stdout
= if rpcenv
.env_type() == RpcEnvironmentType
::CLI { true }
else { false }
;
255 let quiet
= quiet
.unwrap_or(API_METHOD_APT_UPDATE_DATABASE_PARAM_DEFAULT_QUIET
);
257 let upid_str
= WorkerTask
::new_thread("aptupdate", None
, userid
, to_stdout
, move |worker
| {
258 if !quiet { worker.log("starting apt-get update") }
260 // TODO: set proxy /etc/apt/apt.conf.d/76pbsproxy like PVE
262 let mut command
= std
::process
::Command
::new("apt-get");
263 command
.arg("update");
265 let output
= crate::tools
::run_command(command
, None
)?
;
266 if !quiet { worker.log(output) }
268 // TODO: add mail notify for new updates like PVE
276 const SUBDIRS
: SubdirMap
= &[
277 ("update", &Router
::new()
278 .get(&API_METHOD_APT_UPDATE_AVAILABLE
)
279 .post(&API_METHOD_APT_UPDATE_DATABASE
)
283 pub const ROUTER
: Router
= Router
::new()
284 .get(&list_subdirs_api_method
!(SUBDIRS
))