]> git.proxmox.com Git - proxmox-offline-mirror.git/blame - src/config.rs
pool: allow pool re-use
[proxmox-offline-mirror.git] / src / config.rs
CommitLineData
9ecde319
FG
1use std::path::Path;
2
3use anyhow::{bail, Error};
4use lazy_static::lazy_static;
8b267808 5use proxmox_subscription::{sign::ServerBlob, SubscriptionInfo};
9ecde319 6use serde::{Deserialize, Serialize};
f4f8dff0 7
9ecde319
FG
8use proxmox_schema::{api, ApiType, Schema, Updater};
9use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin};
d035ecb5 10use proxmox_sys::fs::{replace_file, CreateOptions};
9ecde319 11
8b267808 12use crate::types::{
b42cad3b
FG
13 ProductType, MEDIA_ID_SCHEMA, MIRROR_ID_SCHEMA, PROXMOX_SERVER_ID_SCHEMA,
14 PROXMOX_SUBSCRIPTION_KEY_SCHEMA,
8b267808 15};
f4f8dff0 16
9ecde319
FG
17#[api(
18 properties: {
19 id: {
20 schema: MIRROR_ID_SCHEMA,
21 },
22 repository: {
23 type: String,
9ecde319
FG
24 },
25 architectures: {
26 type: Array,
9ecde319
FG
27 items: {
28 type: String,
29 description: "Architecture specifier.",
30 },
31 },
d035ecb5 32 "dir": {
9ecde319 33 type: String,
9ecde319
FG
34 },
35 "key-path": {
36 type: String,
9ecde319
FG
37 },
38 verify: {
39 type: bool,
9ecde319
FG
40 },
41 sync: {
42 type: bool,
9ecde319
FG
43 },
44 }
45)]
46#[derive(Clone, Debug, Serialize, Deserialize, Updater)]
47#[serde(rename_all = "kebab-case")]
d035ecb5 48/// Configuration entry for a mirrored repository.
f4f8dff0 49pub struct MirrorConfig {
9ecde319 50 #[updater(skip)]
d035ecb5 51 /// Identifier for this entry.
9ecde319 52 pub id: String,
d035ecb5 53 /// Single repository definition in sources.list format.
9ecde319 54 pub repository: String,
d035ecb5 55 /// List of architectures that should be mirrored.
9ecde319 56 pub architectures: Vec<String>,
d035ecb5
FG
57 /// Path to directory containg mirrored repository.
58 pub dir: String,
59 /// Path to public key file for verifying repository integrity.
9ecde319 60 pub key_path: String,
d035ecb5 61 /// Whether to verify existing files or assume they are valid (IO-intensive).
9ecde319 62 pub verify: bool,
d035ecb5 63 /// Whether to write new files using FSYNC.
9ecde319 64 pub sync: bool,
8b267808
FG
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>,
9ecde319
FG
68}
69
9ecde319
FG
70#[api(
71 properties: {
72 id: {
b42cad3b 73 schema: MEDIA_ID_SCHEMA,
9ecde319
FG
74 },
75 mountpoint: {
76 type: String,
9ecde319
FG
77 },
78 verify: {
79 type: bool,
9ecde319
FG
80 },
81 sync: {
82 type: bool,
9ecde319
FG
83 },
84 mirrors: {
85 type: Array,
9ecde319
FG
86 items: {
87 schema: MIRROR_ID_SCHEMA,
88 },
89 },
90 }
91)]
92#[derive(Debug, Serialize, Deserialize, Updater)]
93#[serde(rename_all = "kebab-case")]
2d13dcfc 94/// Configuration entry for an external medium.
9ecde319
FG
95pub struct MediaConfig {
96 #[updater(skip)]
2d13dcfc 97 /// Identifier for this entry.
9ecde319 98 pub id: String,
2d13dcfc 99 /// Mountpoint where medium is available on mirroring system.
9ecde319 100 pub mountpoint: String,
2d13dcfc 101 /// List of [MirrorConfig] IDs which should be synced to medium.
9ecde319 102 pub mirrors: Vec<String>,
2d13dcfc 103 /// Whether to verify existing files or assume they are valid (IO-intensive).
9ecde319 104 pub verify: bool,
2d13dcfc 105 /// Whether to write new files using FSYNC.
9ecde319
FG
106 pub sync: bool,
107}
108
8b267808
FG
109#[api(
110 properties: {
111 key: {
112 schema: PROXMOX_SUBSCRIPTION_KEY_SCHEMA,
113 },
114 "server-id": {
115 schema: PROXMOX_SERVER_ID_SCHEMA,
116 },
117 description: {
118 type: String,
119 optional: true,
120 },
121 info: {
122 type: String,
123 description: "base64 encoded subscription info - update with 'refresh' command.",
124 optional: true,
125 },
126 },
127)]
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.
131pub struct SubscriptionKey {
132 /// Subscription key
133 #[updater(skip)]
134 pub key: String,
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")]
142 #[updater(skip)]
143 pub info: Option<String>,
144}
145
c76900c9
FG
146impl From<SubscriptionKey> for ServerBlob {
147 fn from(key: SubscriptionKey) -> Self {
148 Self {
149 key: key.key,
150 serverid: key.server_id,
8b267808
FG
151 }
152 }
153}
154
155impl 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!(),
163 }
164 }
165
166 pub fn info(&self) -> Result<Option<SubscriptionInfo>, Error> {
167 match self.info.as_ref() {
168 Some(info) => {
169 let info = base64::decode(info)?;
170 let info = serde_json::from_slice(&info)?;
171 Ok(Some(info))
172 }
173 None => Ok(None),
174 }
175 }
176}
177
9ecde319 178lazy_static! {
fabdaf91 179 pub static ref CONFIG: SectionConfig = init();
9ecde319
FG
180}
181
182fn init() -> SectionConfig {
183 let mut config = SectionConfig::new(&MIRROR_ID_SCHEMA);
184
185 let mirror_schema = match MirrorConfig::API_SCHEMA {
186 Schema::Object(ref obj_schema) => obj_schema,
187 _ => unreachable!(),
188 };
189 let mirror_plugin = SectionConfigPlugin::new(
190 "mirror".to_string(),
191 Some(String::from("id")),
192 mirror_schema,
193 );
194 config.register_plugin(mirror_plugin);
195
196 let media_schema = match MediaConfig::API_SCHEMA {
197 Schema::Object(ref obj_schema) => obj_schema,
198 _ => unreachable!(),
199 };
200 let media_plugin =
201 SectionConfigPlugin::new("medium".to_string(), Some(String::from("id")), media_schema);
202 config.register_plugin(media_plugin);
203
8b267808
FG
204 let key_schema = match SubscriptionKey::API_SCHEMA {
205 Schema::Object(ref obj_schema) => obj_schema,
206 _ => unreachable!(),
207 };
208 let key_plugin = SectionConfigPlugin::new(
209 "subscription".to_string(),
210 Some(String::from("key")),
211 key_schema,
212 );
213 config.register_plugin(key_plugin);
214
9ecde319
FG
215 config
216}
217
2d13dcfc
FG
218/// Lock guard for guarding modifications of config file.
219///
220/// Obtained via [lock_config], should only be dropped once config file should no longer be locked.
9ecde319
FG
221pub struct ConfigLockGuard(std::fs::File);
222
2d13dcfc 223/// Get exclusive lock for config file (in order to make or protect against modifications).
9ecde319
FG
224pub fn lock_config(path: &str) -> Result<ConfigLockGuard, Error> {
225 let path = Path::new(path);
226
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:?}"),
230 };
231 path.push(format!(".{file}.lock"));
232
233 let file = proxmox_sys::fs::open_file_locked(
234 &path,
235 std::time::Duration::new(10, 0),
236 true,
237 CreateOptions::default(),
238 )?;
239 Ok(ConfigLockGuard(file))
240}
241
2d13dcfc 242/// Read config
9ecde319
FG
243pub fn config(path: &str) -> Result<(SectionConfigData, [u8; 32]), Error> {
244 let content =
245 proxmox_sys::fs::file_read_optional_string(path)?.unwrap_or_else(|| "".to_string());
246
247 let digest = openssl::sha::sha256(content.as_bytes());
248 let data = CONFIG.parse(path, &content)?;
249 Ok((data, digest))
250}
251
2d13dcfc 252/// Write config (and verify data matches schema!)
9ecde319
FG
253pub 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)
256}