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