3 use anyhow
::{bail, Error}
;
4 use lazy_static
::lazy_static
;
5 use proxmox_subscription
::{sign::ServerBlob, SubscriptionInfo}
;
6 use serde
::{Deserialize, Serialize}
;
8 use proxmox_schema
::{api, ApiType, Schema, Updater}
;
9 use proxmox_section_config
::{SectionConfig, SectionConfigData, SectionConfigPlugin}
;
10 use proxmox_sys
::fs
::{replace_file, CreateOptions}
;
13 ProductType
, MEDIA_ID_SCHEMA
, MIRROR_ID_SCHEMA
, PROXMOX_SERVER_ID_SCHEMA
,
14 PROXMOX_SUBSCRIPTION_KEY_SCHEMA
,
20 schema
: MIRROR_ID_SCHEMA
,
29 description
: "Architecture specifier.",
51 #[derive(Clone, Debug, Serialize, Deserialize, Updater)]
52 #[serde(rename_all = "kebab-case")]
53 /// Configuration entry for a mirrored repository.
54 pub struct MirrorConfig
{
56 /// Identifier for this entry.
58 /// Single repository definition in sources.list format.
59 pub repository
: String
,
60 /// List of architectures that should be mirrored.
61 pub architectures
: Vec
<String
>,
62 /// Path to directory containg mirrored repository pool. Can be shared by multiple mirrors.
64 /// Path to public key file for verifying repository integrity.
66 /// Whether to verify existing files or assume they are valid (IO-intensive).
68 /// Whether to write new files using FSYNC.
70 /// Use subscription key to access (required for Proxmox Enterprise repositories).
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub use_subscription
: Option
<ProductType
>,
73 /// Whether to downgrade download errors to warnings
75 pub ignore_errors
: bool
,
81 schema
: MEDIA_ID_SCHEMA
,
95 schema
: MIRROR_ID_SCHEMA
,
100 #[derive(Debug, Serialize, Deserialize, Updater)]
101 #[serde(rename_all = "kebab-case")]
102 /// Configuration entry for an external medium.
103 pub struct MediaConfig
{
105 /// Identifier for this entry.
107 /// Mountpoint where medium is available on mirroring system.
108 pub mountpoint
: String
,
109 /// List of [MirrorConfig] IDs which should be synced to medium.
110 pub mirrors
: Vec
<String
>,
111 /// Whether to verify existing files or assume they are valid (IO-intensive).
113 /// Whether to write new files using FSYNC.
120 schema
: PROXMOX_SUBSCRIPTION_KEY_SCHEMA
,
123 schema
: PROXMOX_SERVER_ID_SCHEMA
,
131 description
: "base64 encoded subscription info - update with 'refresh' command.",
136 #[derive(Clone, Debug, Serialize, Deserialize, Updater)]
137 #[serde(rename_all = "kebab-case")]
138 /// Subscription key used for accessing enterprise repositories and for offline subscription activation/renewal.
139 pub struct SubscriptionKey
{
143 /// Server ID for this subscription key
144 pub server_id
: String
,
145 /// Description, e.g. which system this key is deployed on
146 #[serde(skip_serializing_if = "Option::is_none")]
147 pub description
: Option
<String
>,
148 /// Last Subscription Key state
149 #[serde(skip_serializing_if = "Option::is_none")]
151 pub info
: Option
<String
>,
154 impl From
<SubscriptionKey
> for ServerBlob
{
155 fn from(key
: SubscriptionKey
) -> Self {
158 serverid
: key
.server_id
,
163 impl SubscriptionKey
{
164 pub fn product(&self) -> ProductType
{
165 match &self.key
[..3] {
166 "pve" => ProductType
::Pve
,
167 "pmg" => ProductType
::Pmg
,
168 "pbs" => ProductType
::Pbs
,
169 "pom" => ProductType
::Pom
, // TODO replace with actual key prefix
170 _
=> unimplemented
!(),
174 pub fn info(&self) -> Result
<Option
<SubscriptionInfo
>, Error
> {
175 match self.info
.as_ref() {
177 let info
= base64
::decode(info
)?
;
178 let info
= serde_json
::from_slice(&info
)?
;
187 pub static ref CONFIG
: SectionConfig
= init();
190 fn init() -> SectionConfig
{
191 let mut config
= SectionConfig
::new(&MIRROR_ID_SCHEMA
);
193 let mirror_schema
= match MirrorConfig
::API_SCHEMA
{
194 Schema
::Object(ref obj_schema
) => obj_schema
,
197 let mirror_plugin
= SectionConfigPlugin
::new(
198 "mirror".to_string(),
199 Some(String
::from("id")),
202 config
.register_plugin(mirror_plugin
);
204 let media_schema
= match MediaConfig
::API_SCHEMA
{
205 Schema
::Object(ref obj_schema
) => obj_schema
,
209 SectionConfigPlugin
::new("medium".to_string(), Some(String
::from("id")), media_schema
);
210 config
.register_plugin(media_plugin
);
212 let key_schema
= match SubscriptionKey
::API_SCHEMA
{
213 Schema
::Object(ref obj_schema
) => obj_schema
,
216 let key_plugin
= SectionConfigPlugin
::new(
217 "subscription".to_string(),
218 Some(String
::from("key")),
221 config
.register_plugin(key_plugin
);
226 /// Lock guard for guarding modifications of config file.
228 /// Obtained via [lock_config], should only be dropped once config file should no longer be locked.
229 pub struct ConfigLockGuard(std
::fs
::File
);
231 /// Get exclusive lock for config file (in order to make or protect against modifications).
232 pub fn lock_config(path
: &str) -> Result
<ConfigLockGuard
, Error
> {
233 let path
= Path
::new(path
);
235 let (mut path
, file
) = match (path
.parent(), path
.file_name()) {
236 (Some(parent
), Some(file
)) => (parent
.to_path_buf(), file
.to_string_lossy()),
237 _
=> bail
!("Unable to derive lock file name for {path:?}"),
239 path
.push(format
!(".{file}.lock"));
241 let file
= proxmox_sys
::fs
::open_file_locked(
243 std
::time
::Duration
::new(10, 0),
245 CreateOptions
::default(),
247 Ok(ConfigLockGuard(file
))
251 pub fn config(path
: &str) -> Result
<(SectionConfigData
, [u8; 32]), Error
> {
253 proxmox_sys
::fs
::file_read_optional_string(path
)?
.unwrap_or_else(|| "".to_string());
255 let digest
= openssl
::sha
::sha256(content
.as_bytes());
256 let data
= CONFIG
.parse(path
, &content
)?
;
260 /// Write config (and verify data matches schema!)
261 pub fn save_config(path
: &str, data
: &SectionConfigData
) -> Result
<(), Error
> {
262 let raw
= CONFIG
.write(path
, data
)?
;
263 replace_file(path
, raw
.as_bytes(), CreateOptions
::default(), true)