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.",
46 #[derive(Clone, Debug, Serialize, Deserialize, Updater)]
47 #[serde(rename_all = "kebab-case")]
48 /// Configuration entry for a mirrored repository.
49 pub struct MirrorConfig
{
51 /// Identifier for this entry.
53 /// Single repository definition in sources.list format.
54 pub repository
: String
,
55 /// List of architectures that should be mirrored.
56 pub architectures
: Vec
<String
>,
57 /// Path to directory containg mirrored repository pool. Can be shared by multiple mirrors.
59 /// Path to public key file for verifying repository integrity.
61 /// Whether to verify existing files or assume they are valid (IO-intensive).
63 /// Whether to write new files using FSYNC.
65 /// Use subscription key to access (required for Proxmox Enterprise repositories).
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub use_subscription
: Option
<ProductType
>,
73 schema
: MEDIA_ID_SCHEMA
,
87 schema
: MIRROR_ID_SCHEMA
,
92 #[derive(Debug, Serialize, Deserialize, Updater)]
93 #[serde(rename_all = "kebab-case")]
94 /// Configuration entry for an external medium.
95 pub struct MediaConfig
{
97 /// Identifier for this entry.
99 /// Mountpoint where medium is available on mirroring system.
100 pub mountpoint
: String
,
101 /// List of [MirrorConfig] IDs which should be synced to medium.
102 pub mirrors
: Vec
<String
>,
103 /// Whether to verify existing files or assume they are valid (IO-intensive).
105 /// Whether to write new files using FSYNC.
112 schema
: PROXMOX_SUBSCRIPTION_KEY_SCHEMA
,
115 schema
: PROXMOX_SERVER_ID_SCHEMA
,
123 description
: "base64 encoded subscription info - update with 'refresh' command.",
128 #[derive(Clone, Debug, Serialize, Deserialize, Updater)]
129 #[serde(rename_all = "kebab-case")]
130 /// Subscription key used for accessing enterprise repositories and for offline subscription activation/renewal.
131 pub struct SubscriptionKey
{
135 /// Server ID for this subscription key
136 pub server_id
: String
,
137 /// Description, e.g. which system this key is deployed on
138 #[serde(skip_serializing_if = "Option::is_none")]
139 pub description
: Option
<String
>,
140 /// Last Subscription Key state
141 #[serde(skip_serializing_if = "Option::is_none")]
143 pub info
: Option
<String
>,
146 impl From
<SubscriptionKey
> for ServerBlob
{
147 fn from(key
: SubscriptionKey
) -> Self {
150 serverid
: key
.server_id
,
155 impl SubscriptionKey
{
156 pub fn product(&self) -> ProductType
{
157 match &self.key
[..3] {
158 "pve" => ProductType
::Pve
,
159 "pmg" => ProductType
::Pmg
,
160 "pbs" => ProductType
::Pbs
,
161 "pom" => ProductType
::Pom
, // TODO replace with actual key prefix
162 _
=> unimplemented
!(),
166 pub fn info(&self) -> Result
<Option
<SubscriptionInfo
>, Error
> {
167 match self.info
.as_ref() {
169 let info
= base64
::decode(info
)?
;
170 let info
= serde_json
::from_slice(&info
)?
;
179 pub static ref CONFIG
: SectionConfig
= init();
182 fn init() -> SectionConfig
{
183 let mut config
= SectionConfig
::new(&MIRROR_ID_SCHEMA
);
185 let mirror_schema
= match MirrorConfig
::API_SCHEMA
{
186 Schema
::Object(ref obj_schema
) => obj_schema
,
189 let mirror_plugin
= SectionConfigPlugin
::new(
190 "mirror".to_string(),
191 Some(String
::from("id")),
194 config
.register_plugin(mirror_plugin
);
196 let media_schema
= match MediaConfig
::API_SCHEMA
{
197 Schema
::Object(ref obj_schema
) => obj_schema
,
201 SectionConfigPlugin
::new("medium".to_string(), Some(String
::from("id")), media_schema
);
202 config
.register_plugin(media_plugin
);
204 let key_schema
= match SubscriptionKey
::API_SCHEMA
{
205 Schema
::Object(ref obj_schema
) => obj_schema
,
208 let key_plugin
= SectionConfigPlugin
::new(
209 "subscription".to_string(),
210 Some(String
::from("key")),
213 config
.register_plugin(key_plugin
);
218 /// Lock guard for guarding modifications of config file.
220 /// Obtained via [lock_config], should only be dropped once config file should no longer be locked.
221 pub struct ConfigLockGuard(std
::fs
::File
);
223 /// Get exclusive lock for config file (in order to make or protect against modifications).
224 pub fn lock_config(path
: &str) -> Result
<ConfigLockGuard
, Error
> {
225 let path
= Path
::new(path
);
227 let (mut path
, file
) = match (path
.parent(), path
.file_name()) {
228 (Some(parent
), Some(file
)) => (parent
.to_path_buf(), file
.to_string_lossy()),
229 _
=> bail
!("Unable to derive lock file name for {path:?}"),
231 path
.push(format
!(".{file}.lock"));
233 let file
= proxmox_sys
::fs
::open_file_locked(
235 std
::time
::Duration
::new(10, 0),
237 CreateOptions
::default(),
239 Ok(ConfigLockGuard(file
))
243 pub fn config(path
: &str) -> Result
<(SectionConfigData
, [u8; 32]), Error
> {
245 proxmox_sys
::fs
::file_read_optional_string(path
)?
.unwrap_or_else(|| "".to_string());
247 let digest
= openssl
::sha
::sha256(content
.as_bytes());
248 let data
= CONFIG
.parse(path
, &content
)?
;
252 /// Write config (and verify data matches schema!)
253 pub fn save_config(path
: &str, data
: &SectionConfigData
) -> Result
<(), Error
> {
254 let raw
= CONFIG
.write(path
, data
)?
;
255 replace_file(path
, raw
.as_bytes(), CreateOptions
::default(), true)