]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/types/mod.rs
api2/types: add type_text to DATASTORE_MAP_FORMAT
[proxmox-backup.git] / src / api2 / types / mod.rs
1 //! API Type Definitions
2
3 use anyhow::bail;
4 use serde::{Deserialize, Serialize};
5
6 use proxmox::api::{api, schema::*};
7 use proxmox::const_regex;
8 use proxmox::{IPRE, IPRE_BRACKET, IPV4RE, IPV6RE, IPV4OCTET, IPV6H16, IPV6LS32};
9
10 use crate::{
11 backup::{
12 CryptMode,
13 Fingerprint,
14 BACKUP_ID_REGEX,
15 DirEntryAttribute,
16 CatalogEntryType,
17 },
18 server::UPID,
19 config::acl::Role,
20 };
21
22 #[macro_use]
23 mod macros;
24
25 #[macro_use]
26 mod userid;
27 pub use userid::{Realm, RealmRef};
28 pub use userid::{Tokenname, TokennameRef};
29 pub use userid::{Username, UsernameRef};
30 pub use userid::Userid;
31 pub use userid::Authid;
32 pub use userid::{PROXMOX_TOKEN_ID_SCHEMA, PROXMOX_TOKEN_NAME_SCHEMA, PROXMOX_GROUP_ID_SCHEMA};
33
34 mod tape;
35 pub use tape::*;
36
37 // File names: may not contain slashes, may not start with "."
38 pub const FILENAME_FORMAT: ApiStringFormat = ApiStringFormat::VerifyFn(|name| {
39 if name.starts_with('.') {
40 bail!("file names may not start with '.'");
41 }
42 if name.contains('/') {
43 bail!("file names may not contain slashes");
44 }
45 Ok(())
46 });
47
48 macro_rules! DNS_LABEL { () => (r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)") }
49 macro_rules! DNS_NAME { () => (concat!(r"(?:(?:", DNS_LABEL!() , r"\.)*", DNS_LABEL!(), ")")) }
50
51 macro_rules! CIDR_V4_REGEX_STR { () => (concat!(r"(?:", IPV4RE!(), r"/\d{1,2})$")) }
52 macro_rules! CIDR_V6_REGEX_STR { () => (concat!(r"(?:", IPV6RE!(), r"/\d{1,3})$")) }
53
54 const_regex!{
55 pub IP_V4_REGEX = concat!(r"^", IPV4RE!(), r"$");
56 pub IP_V6_REGEX = concat!(r"^", IPV6RE!(), r"$");
57 pub IP_REGEX = concat!(r"^", IPRE!(), r"$");
58 pub CIDR_V4_REGEX = concat!(r"^", CIDR_V4_REGEX_STR!(), r"$");
59 pub CIDR_V6_REGEX = concat!(r"^", CIDR_V6_REGEX_STR!(), r"$");
60 pub CIDR_REGEX = concat!(r"^(?:", CIDR_V4_REGEX_STR!(), "|", CIDR_V6_REGEX_STR!(), r")$");
61
62 pub SHA256_HEX_REGEX = r"^[a-f0-9]{64}$"; // fixme: define in common_regex ?
63 pub SYSTEMD_DATETIME_REGEX = r"^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$"; // fixme: define in common_regex ?
64
65 pub PASSWORD_REGEX = r"^[[:^cntrl:]]*$"; // everything but control characters
66
67 /// Regex for safe identifiers.
68 ///
69 /// This
70 /// [article](https://dwheeler.com/essays/fixing-unix-linux-filenames.html)
71 /// contains further information why it is reasonable to restict
72 /// names this way. This is not only useful for filenames, but for
73 /// any identifier command line tools work with.
74 pub PROXMOX_SAFE_ID_REGEX = concat!(r"^", PROXMOX_SAFE_ID_REGEX_STR!(), r"$");
75
76 /// Regex for verification jobs 'DATASTORE:ACTUAL_JOB_ID'
77 pub VERIFICATION_JOB_WORKER_ID_REGEX = concat!(r"^(", PROXMOX_SAFE_ID_REGEX_STR!(), r"):");
78 /// Regex for sync jobs 'REMOTE:REMOTE_DATASTORE:LOCAL_DATASTORE:ACTUAL_JOB_ID'
79 pub SYNC_JOB_WORKER_ID_REGEX = concat!(r"^(", PROXMOX_SAFE_ID_REGEX_STR!(), r"):(", PROXMOX_SAFE_ID_REGEX_STR!(), r"):(", PROXMOX_SAFE_ID_REGEX_STR!(), r"):");
80
81 pub SINGLE_LINE_COMMENT_REGEX = r"^[[:^cntrl:]]*$";
82
83 pub HOSTNAME_REGEX = r"^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)$";
84
85 pub DNS_NAME_REGEX = concat!(r"^", DNS_NAME!(), r"$");
86
87 pub DNS_NAME_OR_IP_REGEX = concat!(r"^(?:", DNS_NAME!(), "|", IPRE!(), r")$");
88
89 pub BACKUP_REPO_URL_REGEX = concat!(r"^^(?:(?:(", USER_ID_REGEX_STR!(), "|", APITOKEN_ID_REGEX_STR!(), ")@)?(", DNS_NAME!(), "|", IPRE_BRACKET!() ,"):)?(?:([0-9]{1,5}):)?(", PROXMOX_SAFE_ID_REGEX_STR!(), r")$");
90
91 pub FINGERPRINT_SHA256_REGEX = r"^(?:[0-9a-fA-F][0-9a-fA-F])(?::[0-9a-fA-F][0-9a-fA-F]){31}$";
92
93 pub ACL_PATH_REGEX = concat!(r"^(?:/|", r"(?:/", PROXMOX_SAFE_ID_REGEX_STR!(), ")+", r")$");
94
95 pub SUBSCRIPTION_KEY_REGEX = concat!(r"^pbs(?:[cbsp])-[0-9a-f]{10}$");
96
97 pub BLOCKDEVICE_NAME_REGEX = r"^(:?(:?h|s|x?v)d[a-z]+)|(:?nvme\d+n\d+)$";
98
99 pub ZPOOL_NAME_REGEX = r"^[a-zA-Z][a-z0-9A-Z\-_.:]+$";
100
101 pub UUID_REGEX = r"^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$";
102
103 pub DATASTORE_MAP_REGEX = concat!(r"(:?", PROXMOX_SAFE_ID_REGEX_STR!(), r"=)?", PROXMOX_SAFE_ID_REGEX_STR!());
104 }
105
106 pub const SYSTEMD_DATETIME_FORMAT: ApiStringFormat =
107 ApiStringFormat::Pattern(&SYSTEMD_DATETIME_REGEX);
108
109 pub const IP_V4_FORMAT: ApiStringFormat =
110 ApiStringFormat::Pattern(&IP_V4_REGEX);
111
112 pub const IP_V6_FORMAT: ApiStringFormat =
113 ApiStringFormat::Pattern(&IP_V6_REGEX);
114
115 pub const IP_FORMAT: ApiStringFormat =
116 ApiStringFormat::Pattern(&IP_REGEX);
117
118 pub const PVE_CONFIG_DIGEST_FORMAT: ApiStringFormat =
119 ApiStringFormat::Pattern(&SHA256_HEX_REGEX);
120
121 pub const FINGERPRINT_SHA256_FORMAT: ApiStringFormat =
122 ApiStringFormat::Pattern(&FINGERPRINT_SHA256_REGEX);
123
124 pub const PROXMOX_SAFE_ID_FORMAT: ApiStringFormat =
125 ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX);
126
127 pub const BACKUP_ID_FORMAT: ApiStringFormat =
128 ApiStringFormat::Pattern(&BACKUP_ID_REGEX);
129
130 pub const UUID_FORMAT: ApiStringFormat =
131 ApiStringFormat::Pattern(&UUID_REGEX);
132
133 pub const SINGLE_LINE_COMMENT_FORMAT: ApiStringFormat =
134 ApiStringFormat::Pattern(&SINGLE_LINE_COMMENT_REGEX);
135
136 pub const HOSTNAME_FORMAT: ApiStringFormat =
137 ApiStringFormat::Pattern(&HOSTNAME_REGEX);
138
139 pub const DNS_NAME_FORMAT: ApiStringFormat =
140 ApiStringFormat::Pattern(&DNS_NAME_REGEX);
141
142 pub const DNS_NAME_OR_IP_FORMAT: ApiStringFormat =
143 ApiStringFormat::Pattern(&DNS_NAME_OR_IP_REGEX);
144
145 pub const PASSWORD_FORMAT: ApiStringFormat =
146 ApiStringFormat::Pattern(&PASSWORD_REGEX);
147
148 pub const ACL_PATH_FORMAT: ApiStringFormat =
149 ApiStringFormat::Pattern(&ACL_PATH_REGEX);
150
151 pub const NETWORK_INTERFACE_FORMAT: ApiStringFormat =
152 ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX);
153
154 pub const CIDR_V4_FORMAT: ApiStringFormat =
155 ApiStringFormat::Pattern(&CIDR_V4_REGEX);
156
157 pub const CIDR_V6_FORMAT: ApiStringFormat =
158 ApiStringFormat::Pattern(&CIDR_V6_REGEX);
159
160 pub const CIDR_FORMAT: ApiStringFormat =
161 ApiStringFormat::Pattern(&CIDR_REGEX);
162
163 pub const SUBSCRIPTION_KEY_FORMAT: ApiStringFormat =
164 ApiStringFormat::Pattern(&SUBSCRIPTION_KEY_REGEX);
165
166 pub const BLOCKDEVICE_NAME_FORMAT: ApiStringFormat =
167 ApiStringFormat::Pattern(&BLOCKDEVICE_NAME_REGEX);
168
169 pub const DATASTORE_MAP_FORMAT: ApiStringFormat =
170 ApiStringFormat::Pattern(&DATASTORE_MAP_REGEX);
171
172 pub const PASSWORD_SCHEMA: Schema = StringSchema::new("Password.")
173 .format(&PASSWORD_FORMAT)
174 .min_length(1)
175 .max_length(1024)
176 .schema();
177
178 pub const PBS_PASSWORD_SCHEMA: Schema = StringSchema::new("User Password.")
179 .format(&PASSWORD_FORMAT)
180 .min_length(5)
181 .max_length(64)
182 .schema();
183
184 pub const CERT_FINGERPRINT_SHA256_SCHEMA: Schema = StringSchema::new(
185 "X509 certificate fingerprint (sha256)."
186 )
187 .format(&FINGERPRINT_SHA256_FORMAT)
188 .schema();
189
190 pub const TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA: Schema = StringSchema::new(
191 "Tape encryption key fingerprint (sha256)."
192 )
193 .format(&FINGERPRINT_SHA256_FORMAT)
194 .schema();
195
196 pub const PROXMOX_CONFIG_DIGEST_SCHEMA: Schema = StringSchema::new(
197 "Prevent changes if current configuration file has different \
198 SHA256 digest. This can be used to prevent concurrent \
199 modifications."
200 )
201 .format(&PVE_CONFIG_DIGEST_FORMAT) .schema();
202
203
204 pub const CHUNK_DIGEST_FORMAT: ApiStringFormat =
205 ApiStringFormat::Pattern(&SHA256_HEX_REGEX);
206
207 pub const CHUNK_DIGEST_SCHEMA: Schema = StringSchema::new("Chunk digest (SHA256).")
208 .format(&CHUNK_DIGEST_FORMAT)
209 .schema();
210
211 pub const NODE_SCHEMA: Schema = StringSchema::new("Node name (or 'localhost')")
212 .format(&ApiStringFormat::VerifyFn(|node| {
213 if node == "localhost" || node == proxmox::tools::nodename() {
214 Ok(())
215 } else {
216 bail!("no such node '{}'", node);
217 }
218 }))
219 .schema();
220
221 pub const SEARCH_DOMAIN_SCHEMA: Schema =
222 StringSchema::new("Search domain for host-name lookup.").schema();
223
224 pub const FIRST_DNS_SERVER_SCHEMA: Schema =
225 StringSchema::new("First name server IP address.")
226 .format(&IP_FORMAT)
227 .schema();
228
229 pub const SECOND_DNS_SERVER_SCHEMA: Schema =
230 StringSchema::new("Second name server IP address.")
231 .format(&IP_FORMAT)
232 .schema();
233
234 pub const THIRD_DNS_SERVER_SCHEMA: Schema =
235 StringSchema::new("Third name server IP address.")
236 .format(&IP_FORMAT)
237 .schema();
238
239 pub const IP_V4_SCHEMA: Schema =
240 StringSchema::new("IPv4 address.")
241 .format(&IP_V4_FORMAT)
242 .max_length(15)
243 .schema();
244
245 pub const IP_V6_SCHEMA: Schema =
246 StringSchema::new("IPv6 address.")
247 .format(&IP_V6_FORMAT)
248 .max_length(39)
249 .schema();
250
251 pub const IP_SCHEMA: Schema =
252 StringSchema::new("IP (IPv4 or IPv6) address.")
253 .format(&IP_FORMAT)
254 .max_length(39)
255 .schema();
256
257 pub const CIDR_V4_SCHEMA: Schema =
258 StringSchema::new("IPv4 address with netmask (CIDR notation).")
259 .format(&CIDR_V4_FORMAT)
260 .max_length(18)
261 .schema();
262
263 pub const CIDR_V6_SCHEMA: Schema =
264 StringSchema::new("IPv6 address with netmask (CIDR notation).")
265 .format(&CIDR_V6_FORMAT)
266 .max_length(43)
267 .schema();
268
269 pub const CIDR_SCHEMA: Schema =
270 StringSchema::new("IP address (IPv4 or IPv6) with netmask (CIDR notation).")
271 .format(&CIDR_FORMAT)
272 .max_length(43)
273 .schema();
274
275 pub const TIME_ZONE_SCHEMA: Schema = StringSchema::new(
276 "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.")
277 .format(&SINGLE_LINE_COMMENT_FORMAT)
278 .min_length(2)
279 .max_length(64)
280 .schema();
281
282 pub const ACL_PATH_SCHEMA: Schema = StringSchema::new(
283 "Access control path.")
284 .format(&ACL_PATH_FORMAT)
285 .min_length(1)
286 .max_length(128)
287 .schema();
288
289 pub const ACL_PROPAGATE_SCHEMA: Schema = BooleanSchema::new(
290 "Allow to propagate (inherit) permissions.")
291 .default(true)
292 .schema();
293
294 pub const ACL_UGID_TYPE_SCHEMA: Schema = StringSchema::new(
295 "Type of 'ugid' property.")
296 .format(&ApiStringFormat::Enum(&[
297 EnumEntry::new("user", "User"),
298 EnumEntry::new("group", "Group")]))
299 .schema();
300
301 #[api(
302 properties: {
303 propagate: {
304 schema: ACL_PROPAGATE_SCHEMA,
305 },
306 path: {
307 schema: ACL_PATH_SCHEMA,
308 },
309 ugid_type: {
310 schema: ACL_UGID_TYPE_SCHEMA,
311 },
312 ugid: {
313 type: String,
314 description: "User or Group ID.",
315 },
316 roleid: {
317 type: Role,
318 }
319 }
320 )]
321 #[derive(Serialize, Deserialize)]
322 /// ACL list entry.
323 pub struct AclListItem {
324 pub path: String,
325 pub ugid: String,
326 pub ugid_type: String,
327 pub propagate: bool,
328 pub roleid: String,
329 }
330
331 pub const BACKUP_ARCHIVE_NAME_SCHEMA: Schema =
332 StringSchema::new("Backup archive name.")
333 .format(&PROXMOX_SAFE_ID_FORMAT)
334 .schema();
335
336 pub const BACKUP_TYPE_SCHEMA: Schema =
337 StringSchema::new("Backup type.")
338 .format(&ApiStringFormat::Enum(&[
339 EnumEntry::new("vm", "Virtual Machine Backup"),
340 EnumEntry::new("ct", "Container Backup"),
341 EnumEntry::new("host", "Host Backup")]))
342 .schema();
343
344 pub const BACKUP_ID_SCHEMA: Schema =
345 StringSchema::new("Backup ID.")
346 .format(&BACKUP_ID_FORMAT)
347 .schema();
348
349 pub const BACKUP_TIME_SCHEMA: Schema =
350 IntegerSchema::new("Backup time (Unix epoch.)")
351 .minimum(1_547_797_308)
352 .schema();
353
354 pub const UPID_SCHEMA: Schema = StringSchema::new("Unique Process/Task ID.")
355 .max_length(256)
356 .schema();
357
358 pub const DATASTORE_SCHEMA: Schema = StringSchema::new("Datastore name.")
359 .format(&PROXMOX_SAFE_ID_FORMAT)
360 .min_length(3)
361 .max_length(32)
362 .schema();
363
364 pub const DATASTORE_MAP_SCHEMA: Schema = StringSchema::new("Datastore mapping.")
365 .format(&DATASTORE_MAP_FORMAT)
366 .min_length(3)
367 .max_length(65)
368 .type_text("(<source>=)?<target>")
369 .schema();
370
371 pub const DATASTORE_MAP_ARRAY_SCHEMA: Schema = ArraySchema::new(
372 "Datastore mapping list.", &DATASTORE_MAP_SCHEMA)
373 .schema();
374
375 pub const DATASTORE_MAP_LIST_SCHEMA: Schema = StringSchema::new(
376 "A list of Datastore mappings (or single datastore), comma separated. \
377 For example 'a=b,e' maps the source datastore 'a' to target 'b and \
378 all other sources to the default 'e'. If no default is given, only the \
379 specified sources are mapped.")
380 .format(&ApiStringFormat::PropertyString(&DATASTORE_MAP_ARRAY_SCHEMA))
381 .schema();
382
383 pub const MEDIA_SET_UUID_SCHEMA: Schema =
384 StringSchema::new("MediaSet Uuid (We use the all-zero Uuid to reseve an empty media for a specific pool).")
385 .format(&UUID_FORMAT)
386 .schema();
387
388 pub const MEDIA_UUID_SCHEMA: Schema =
389 StringSchema::new("Media Uuid.")
390 .format(&UUID_FORMAT)
391 .schema();
392
393 pub const SYNC_SCHEDULE_SCHEMA: Schema = StringSchema::new(
394 "Run sync job at specified schedule.")
395 .format(&ApiStringFormat::VerifyFn(crate::tools::systemd::time::verify_calendar_event))
396 .type_text("<calendar-event>")
397 .schema();
398
399 pub const GC_SCHEDULE_SCHEMA: Schema = StringSchema::new(
400 "Run garbage collection job at specified schedule.")
401 .format(&ApiStringFormat::VerifyFn(crate::tools::systemd::time::verify_calendar_event))
402 .type_text("<calendar-event>")
403 .schema();
404
405 pub const PRUNE_SCHEDULE_SCHEMA: Schema = StringSchema::new(
406 "Run prune job at specified schedule.")
407 .format(&ApiStringFormat::VerifyFn(crate::tools::systemd::time::verify_calendar_event))
408 .type_text("<calendar-event>")
409 .schema();
410
411 pub const VERIFICATION_SCHEDULE_SCHEMA: Schema = StringSchema::new(
412 "Run verify job at specified schedule.")
413 .format(&ApiStringFormat::VerifyFn(crate::tools::systemd::time::verify_calendar_event))
414 .type_text("<calendar-event>")
415 .schema();
416
417 pub const REMOTE_ID_SCHEMA: Schema = StringSchema::new("Remote ID.")
418 .format(&PROXMOX_SAFE_ID_FORMAT)
419 .min_length(3)
420 .max_length(32)
421 .schema();
422
423 pub const JOB_ID_SCHEMA: Schema = StringSchema::new("Job ID.")
424 .format(&PROXMOX_SAFE_ID_FORMAT)
425 .min_length(3)
426 .max_length(32)
427 .schema();
428
429 pub const REMOVE_VANISHED_BACKUPS_SCHEMA: Schema = BooleanSchema::new(
430 "Delete vanished backups. This remove the local copy if the remote backup was deleted.")
431 .default(true)
432 .schema();
433
434 pub const IGNORE_VERIFIED_BACKUPS_SCHEMA: Schema = BooleanSchema::new(
435 "Do not verify backups that are already verified if their verification is not outdated.")
436 .default(true)
437 .schema();
438
439 pub const VERIFICATION_OUTDATED_AFTER_SCHEMA: Schema = IntegerSchema::new(
440 "Days after that a verification becomes outdated")
441 .minimum(1)
442 .schema();
443
444 pub const SINGLE_LINE_COMMENT_SCHEMA: Schema = StringSchema::new("Comment (single line).")
445 .format(&SINGLE_LINE_COMMENT_FORMAT)
446 .schema();
447
448 pub const HOSTNAME_SCHEMA: Schema = StringSchema::new("Hostname (as defined in RFC1123).")
449 .format(&HOSTNAME_FORMAT)
450 .schema();
451
452 pub const DNS_NAME_OR_IP_SCHEMA: Schema = StringSchema::new("DNS name or IP address.")
453 .format(&DNS_NAME_OR_IP_FORMAT)
454 .schema();
455
456 pub const SUBSCRIPTION_KEY_SCHEMA: Schema = StringSchema::new("Proxmox Backup Server subscription key.")
457 .format(&SUBSCRIPTION_KEY_FORMAT)
458 .min_length(15)
459 .max_length(16)
460 .schema();
461
462 pub const BLOCKDEVICE_NAME_SCHEMA: Schema = StringSchema::new("Block device name (/sys/block/<name>).")
463 .format(&BLOCKDEVICE_NAME_FORMAT)
464 .min_length(3)
465 .max_length(64)
466 .schema();
467
468 // Complex type definitions
469
470 #[api(
471 properties: {
472 store: {
473 schema: DATASTORE_SCHEMA,
474 },
475 comment: {
476 optional: true,
477 schema: SINGLE_LINE_COMMENT_SCHEMA,
478 },
479 },
480 )]
481 #[derive(Serialize, Deserialize)]
482 #[serde(rename_all="kebab-case")]
483 /// Basic information about a datastore.
484 pub struct DataStoreListItem {
485 pub store: String,
486 pub comment: Option<String>,
487 }
488
489 #[api(
490 properties: {
491 "backup-type": {
492 schema: BACKUP_TYPE_SCHEMA,
493 },
494 "backup-id": {
495 schema: BACKUP_ID_SCHEMA,
496 },
497 "last-backup": {
498 schema: BACKUP_TIME_SCHEMA,
499 },
500 "backup-count": {
501 type: Integer,
502 },
503 files: {
504 items: {
505 schema: BACKUP_ARCHIVE_NAME_SCHEMA
506 },
507 },
508 owner: {
509 type: Authid,
510 optional: true,
511 },
512 },
513 )]
514 #[derive(Serialize, Deserialize)]
515 #[serde(rename_all="kebab-case")]
516 /// Basic information about a backup group.
517 pub struct GroupListItem {
518 pub backup_type: String, // enum
519 pub backup_id: String,
520 pub last_backup: i64,
521 /// Number of contained snapshots
522 pub backup_count: u64,
523 /// List of contained archive files.
524 pub files: Vec<String>,
525 /// The owner of group
526 #[serde(skip_serializing_if="Option::is_none")]
527 pub owner: Option<Authid>,
528 }
529
530 #[api()]
531 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
532 #[serde(rename_all = "lowercase")]
533 /// Result of a verify operation.
534 pub enum VerifyState {
535 /// Verification was successful
536 Ok,
537 /// Verification reported one or more errors
538 Failed,
539 }
540
541 #[api(
542 properties: {
543 upid: {
544 schema: UPID_SCHEMA
545 },
546 state: {
547 type: VerifyState
548 },
549 },
550 )]
551 #[derive(Serialize, Deserialize)]
552 /// Task properties.
553 pub struct SnapshotVerifyState {
554 /// UPID of the verify task
555 pub upid: UPID,
556 /// State of the verification. Enum.
557 pub state: VerifyState,
558 }
559
560 #[api(
561 properties: {
562 "backup-type": {
563 schema: BACKUP_TYPE_SCHEMA,
564 },
565 "backup-id": {
566 schema: BACKUP_ID_SCHEMA,
567 },
568 "backup-time": {
569 schema: BACKUP_TIME_SCHEMA,
570 },
571 comment: {
572 schema: SINGLE_LINE_COMMENT_SCHEMA,
573 optional: true,
574 },
575 verification: {
576 type: SnapshotVerifyState,
577 optional: true,
578 },
579 fingerprint: {
580 type: String,
581 optional: true,
582 },
583 files: {
584 items: {
585 schema: BACKUP_ARCHIVE_NAME_SCHEMA
586 },
587 },
588 owner: {
589 type: Authid,
590 optional: true,
591 },
592 },
593 )]
594 #[derive(Serialize, Deserialize)]
595 #[serde(rename_all="kebab-case")]
596 /// Basic information about backup snapshot.
597 pub struct SnapshotListItem {
598 pub backup_type: String, // enum
599 pub backup_id: String,
600 pub backup_time: i64,
601 /// The first line from manifest "notes"
602 #[serde(skip_serializing_if="Option::is_none")]
603 pub comment: Option<String>,
604 /// The result of the last run verify task
605 #[serde(skip_serializing_if="Option::is_none")]
606 pub verification: Option<SnapshotVerifyState>,
607 /// Fingerprint of encryption key
608 #[serde(skip_serializing_if="Option::is_none")]
609 pub fingerprint: Option<Fingerprint>,
610 /// List of contained archive files.
611 pub files: Vec<BackupContent>,
612 /// Overall snapshot size (sum of all archive sizes).
613 #[serde(skip_serializing_if="Option::is_none")]
614 pub size: Option<u64>,
615 /// The owner of the snapshots group
616 #[serde(skip_serializing_if="Option::is_none")]
617 pub owner: Option<Authid>,
618 }
619
620 #[api(
621 properties: {
622 "backup-type": {
623 schema: BACKUP_TYPE_SCHEMA,
624 },
625 "backup-id": {
626 schema: BACKUP_ID_SCHEMA,
627 },
628 "backup-time": {
629 schema: BACKUP_TIME_SCHEMA,
630 },
631 },
632 )]
633 #[derive(Serialize, Deserialize)]
634 #[serde(rename_all="kebab-case")]
635 /// Prune result.
636 pub struct PruneListItem {
637 pub backup_type: String, // enum
638 pub backup_id: String,
639 pub backup_time: i64,
640 /// Keep snapshot
641 pub keep: bool,
642 }
643
644 pub const PRUNE_SCHEMA_KEEP_DAILY: Schema = IntegerSchema::new(
645 "Number of daily backups to keep.")
646 .minimum(1)
647 .schema();
648
649 pub const PRUNE_SCHEMA_KEEP_HOURLY: Schema = IntegerSchema::new(
650 "Number of hourly backups to keep.")
651 .minimum(1)
652 .schema();
653
654 pub const PRUNE_SCHEMA_KEEP_LAST: Schema = IntegerSchema::new(
655 "Number of backups to keep.")
656 .minimum(1)
657 .schema();
658
659 pub const PRUNE_SCHEMA_KEEP_MONTHLY: Schema = IntegerSchema::new(
660 "Number of monthly backups to keep.")
661 .minimum(1)
662 .schema();
663
664 pub const PRUNE_SCHEMA_KEEP_WEEKLY: Schema = IntegerSchema::new(
665 "Number of weekly backups to keep.")
666 .minimum(1)
667 .schema();
668
669 pub const PRUNE_SCHEMA_KEEP_YEARLY: Schema = IntegerSchema::new(
670 "Number of yearly backups to keep.")
671 .minimum(1)
672 .schema();
673
674 #[api(
675 properties: {
676 "filename": {
677 schema: BACKUP_ARCHIVE_NAME_SCHEMA,
678 },
679 "crypt-mode": {
680 type: CryptMode,
681 optional: true,
682 },
683 },
684 )]
685 #[derive(Serialize, Deserialize)]
686 #[serde(rename_all="kebab-case")]
687 /// Basic information about archive files inside a backup snapshot.
688 pub struct BackupContent {
689 pub filename: String,
690 /// Info if file is encrypted, signed, or neither.
691 #[serde(skip_serializing_if="Option::is_none")]
692 pub crypt_mode: Option<CryptMode>,
693 /// Archive size (from backup manifest).
694 #[serde(skip_serializing_if="Option::is_none")]
695 pub size: Option<u64>,
696 }
697
698 #[api(
699 properties: {
700 "upid": {
701 optional: true,
702 schema: UPID_SCHEMA,
703 },
704 },
705 )]
706 #[derive(Clone, Serialize, Deserialize)]
707 #[serde(rename_all="kebab-case")]
708 /// Garbage collection status.
709 pub struct GarbageCollectionStatus {
710 pub upid: Option<String>,
711 /// Number of processed index files.
712 pub index_file_count: usize,
713 /// Sum of bytes referred by index files.
714 pub index_data_bytes: u64,
715 /// Bytes used on disk.
716 pub disk_bytes: u64,
717 /// Chunks used on disk.
718 pub disk_chunks: usize,
719 /// Sum of removed bytes.
720 pub removed_bytes: u64,
721 /// Number of removed chunks.
722 pub removed_chunks: usize,
723 /// Sum of pending bytes (pending removal - kept for safety).
724 pub pending_bytes: u64,
725 /// Number of pending chunks (pending removal - kept for safety).
726 pub pending_chunks: usize,
727 /// Number of chunks marked as .bad by verify that have been removed by GC.
728 pub removed_bad: usize,
729 /// Number of chunks still marked as .bad after garbage collection.
730 pub still_bad: usize,
731 }
732
733 impl Default for GarbageCollectionStatus {
734 fn default() -> Self {
735 GarbageCollectionStatus {
736 upid: None,
737 index_file_count: 0,
738 index_data_bytes: 0,
739 disk_bytes: 0,
740 disk_chunks: 0,
741 removed_bytes: 0,
742 removed_chunks: 0,
743 pending_bytes: 0,
744 pending_chunks: 0,
745 removed_bad: 0,
746 still_bad: 0,
747 }
748 }
749 }
750
751
752 #[api()]
753 #[derive(Serialize, Deserialize)]
754 /// Storage space usage information.
755 pub struct StorageStatus {
756 /// Total space (bytes).
757 pub total: u64,
758 /// Used space (bytes).
759 pub used: u64,
760 /// Available space (bytes).
761 pub avail: u64,
762 }
763
764 #[api()]
765 #[derive(Serialize, Deserialize, Default)]
766 /// Backup Type group/snapshot counts.
767 pub struct TypeCounts {
768 /// The number of groups of the type.
769 pub groups: u64,
770 /// The number of snapshots of the type.
771 pub snapshots: u64,
772 }
773
774 #[api(
775 properties: {
776 ct: {
777 type: TypeCounts,
778 optional: true,
779 },
780 host: {
781 type: TypeCounts,
782 optional: true,
783 },
784 vm: {
785 type: TypeCounts,
786 optional: true,
787 },
788 other: {
789 type: TypeCounts,
790 optional: true,
791 },
792 },
793 )]
794 #[derive(Serialize, Deserialize, Default)]
795 /// Counts of groups/snapshots per BackupType.
796 pub struct Counts {
797 /// The counts for CT backups
798 pub ct: Option<TypeCounts>,
799 /// The counts for Host backups
800 pub host: Option<TypeCounts>,
801 /// The counts for VM backups
802 pub vm: Option<TypeCounts>,
803 /// The counts for other backup types
804 pub other: Option<TypeCounts>,
805 }
806
807 #[api(
808 properties: {
809 "gc-status": {
810 type: GarbageCollectionStatus,
811 optional: true,
812 },
813 counts: {
814 type: Counts,
815 optional: true,
816 },
817 },
818 )]
819 #[derive(Serialize, Deserialize)]
820 #[serde(rename_all="kebab-case")]
821 /// Overall Datastore status and useful information.
822 pub struct DataStoreStatus {
823 /// Total space (bytes).
824 pub total: u64,
825 /// Used space (bytes).
826 pub used: u64,
827 /// Available space (bytes).
828 pub avail: u64,
829 /// Status of last GC
830 #[serde(skip_serializing_if="Option::is_none")]
831 pub gc_status: Option<GarbageCollectionStatus>,
832 /// Group/Snapshot counts
833 #[serde(skip_serializing_if="Option::is_none")]
834 pub counts: Option<Counts>,
835 }
836
837 #[api(
838 properties: {
839 upid: { schema: UPID_SCHEMA },
840 user: { type: Authid },
841 },
842 )]
843 #[derive(Serialize, Deserialize)]
844 /// Task properties.
845 pub struct TaskListItem {
846 pub upid: String,
847 /// The node name where the task is running on.
848 pub node: String,
849 /// The Unix PID
850 pub pid: i64,
851 /// The task start time (Epoch)
852 pub pstart: u64,
853 /// The task start time (Epoch)
854 pub starttime: i64,
855 /// Worker type (arbitrary ASCII string)
856 pub worker_type: String,
857 /// Worker ID (arbitrary ASCII string)
858 pub worker_id: Option<String>,
859 /// The authenticated entity who started the task
860 pub user: Authid,
861 /// The task end time (Epoch)
862 #[serde(skip_serializing_if="Option::is_none")]
863 pub endtime: Option<i64>,
864 /// Task end status
865 #[serde(skip_serializing_if="Option::is_none")]
866 pub status: Option<String>,
867 }
868
869 impl From<crate::server::TaskListInfo> for TaskListItem {
870 fn from(info: crate::server::TaskListInfo) -> Self {
871 let (endtime, status) = info
872 .state
873 .map_or_else(|| (None, None), |a| (Some(a.endtime()), Some(a.to_string())));
874
875 TaskListItem {
876 upid: info.upid_str,
877 node: "localhost".to_string(),
878 pid: info.upid.pid as i64,
879 pstart: info.upid.pstart,
880 starttime: info.upid.starttime,
881 worker_type: info.upid.worker_type,
882 worker_id: info.upid.worker_id,
883 user: info.upid.auth_id,
884 endtime,
885 status,
886 }
887 }
888 }
889
890 #[api()]
891 #[derive(Eq, PartialEq, Debug, Serialize, Deserialize)]
892 #[serde(rename_all = "lowercase")]
893 pub enum TaskStateType {
894 /// Ok
895 OK,
896 /// Warning
897 Warning,
898 /// Error
899 Error,
900 /// Unknown
901 Unknown,
902 }
903
904 #[api()]
905 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
906 #[serde(rename_all = "lowercase")]
907 /// Node Power command type.
908 pub enum NodePowerCommand {
909 /// Restart the server
910 Reboot,
911 /// Shutdown the server
912 Shutdown,
913 }
914
915 #[api()]
916 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
917 #[serde(rename_all = "lowercase")]
918 /// Interface configuration method
919 pub enum NetworkConfigMethod {
920 /// Configuration is done manually using other tools
921 Manual,
922 /// Define interfaces with statically allocated addresses.
923 Static,
924 /// Obtain an address via DHCP
925 DHCP,
926 /// Define the loopback interface.
927 Loopback,
928 }
929
930 #[api()]
931 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
932 #[serde(rename_all = "kebab-case")]
933 #[allow(non_camel_case_types)]
934 #[repr(u8)]
935 /// Linux Bond Mode
936 pub enum LinuxBondMode {
937 /// Round-robin policy
938 balance_rr = 0,
939 /// Active-backup policy
940 active_backup = 1,
941 /// XOR policy
942 balance_xor = 2,
943 /// Broadcast policy
944 broadcast = 3,
945 /// IEEE 802.3ad Dynamic link aggregation
946 #[serde(rename = "802.3ad")]
947 ieee802_3ad = 4,
948 /// Adaptive transmit load balancing
949 balance_tlb = 5,
950 /// Adaptive load balancing
951 balance_alb = 6,
952 }
953
954 #[api()]
955 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
956 #[serde(rename_all = "kebab-case")]
957 #[allow(non_camel_case_types)]
958 #[repr(u8)]
959 /// Bond Transmit Hash Policy for LACP (802.3ad)
960 pub enum BondXmitHashPolicy {
961 /// Layer 2
962 layer2 = 0,
963 /// Layer 2+3
964 #[serde(rename = "layer2+3")]
965 layer2_3 = 1,
966 /// Layer 3+4
967 #[serde(rename = "layer3+4")]
968 layer3_4 = 2,
969 }
970
971 #[api()]
972 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
973 #[serde(rename_all = "lowercase")]
974 /// Network interface type
975 pub enum NetworkInterfaceType {
976 /// Loopback
977 Loopback,
978 /// Physical Ethernet device
979 Eth,
980 /// Linux Bridge
981 Bridge,
982 /// Linux Bond
983 Bond,
984 /// Linux VLAN (eth.10)
985 Vlan,
986 /// Interface Alias (eth:1)
987 Alias,
988 /// Unknown interface type
989 Unknown,
990 }
991
992 pub const NETWORK_INTERFACE_NAME_SCHEMA: Schema = StringSchema::new("Network interface name.")
993 .format(&NETWORK_INTERFACE_FORMAT)
994 .min_length(1)
995 .max_length(libc::IFNAMSIZ-1)
996 .schema();
997
998 pub const NETWORK_INTERFACE_ARRAY_SCHEMA: Schema = ArraySchema::new(
999 "Network interface list.", &NETWORK_INTERFACE_NAME_SCHEMA)
1000 .schema();
1001
1002 pub const NETWORK_INTERFACE_LIST_SCHEMA: Schema = StringSchema::new(
1003 "A list of network devices, comma separated.")
1004 .format(&ApiStringFormat::PropertyString(&NETWORK_INTERFACE_ARRAY_SCHEMA))
1005 .schema();
1006
1007 #[api(
1008 properties: {
1009 name: {
1010 schema: NETWORK_INTERFACE_NAME_SCHEMA,
1011 },
1012 "type": {
1013 type: NetworkInterfaceType,
1014 },
1015 method: {
1016 type: NetworkConfigMethod,
1017 optional: true,
1018 },
1019 method6: {
1020 type: NetworkConfigMethod,
1021 optional: true,
1022 },
1023 cidr: {
1024 schema: CIDR_V4_SCHEMA,
1025 optional: true,
1026 },
1027 cidr6: {
1028 schema: CIDR_V6_SCHEMA,
1029 optional: true,
1030 },
1031 gateway: {
1032 schema: IP_V4_SCHEMA,
1033 optional: true,
1034 },
1035 gateway6: {
1036 schema: IP_V6_SCHEMA,
1037 optional: true,
1038 },
1039 options: {
1040 description: "Option list (inet)",
1041 type: Array,
1042 items: {
1043 description: "Optional attribute line.",
1044 type: String,
1045 },
1046 },
1047 options6: {
1048 description: "Option list (inet6)",
1049 type: Array,
1050 items: {
1051 description: "Optional attribute line.",
1052 type: String,
1053 },
1054 },
1055 comments: {
1056 description: "Comments (inet, may span multiple lines)",
1057 type: String,
1058 optional: true,
1059 },
1060 comments6: {
1061 description: "Comments (inet6, may span multiple lines)",
1062 type: String,
1063 optional: true,
1064 },
1065 bridge_ports: {
1066 schema: NETWORK_INTERFACE_ARRAY_SCHEMA,
1067 optional: true,
1068 },
1069 slaves: {
1070 schema: NETWORK_INTERFACE_ARRAY_SCHEMA,
1071 optional: true,
1072 },
1073 bond_mode: {
1074 type: LinuxBondMode,
1075 optional: true,
1076 },
1077 "bond-primary": {
1078 schema: NETWORK_INTERFACE_NAME_SCHEMA,
1079 optional: true,
1080 },
1081 bond_xmit_hash_policy: {
1082 type: BondXmitHashPolicy,
1083 optional: true,
1084 },
1085 }
1086 )]
1087 #[derive(Debug, Serialize, Deserialize)]
1088 /// Network Interface configuration
1089 pub struct Interface {
1090 /// Autostart interface
1091 #[serde(rename = "autostart")]
1092 pub autostart: bool,
1093 /// Interface is active (UP)
1094 pub active: bool,
1095 /// Interface name
1096 pub name: String,
1097 /// Interface type
1098 #[serde(rename = "type")]
1099 pub interface_type: NetworkInterfaceType,
1100 #[serde(skip_serializing_if="Option::is_none")]
1101 pub method: Option<NetworkConfigMethod>,
1102 #[serde(skip_serializing_if="Option::is_none")]
1103 pub method6: Option<NetworkConfigMethod>,
1104 #[serde(skip_serializing_if="Option::is_none")]
1105 /// IPv4 address with netmask
1106 pub cidr: Option<String>,
1107 #[serde(skip_serializing_if="Option::is_none")]
1108 /// IPv4 gateway
1109 pub gateway: Option<String>,
1110 #[serde(skip_serializing_if="Option::is_none")]
1111 /// IPv6 address with netmask
1112 pub cidr6: Option<String>,
1113 #[serde(skip_serializing_if="Option::is_none")]
1114 /// IPv6 gateway
1115 pub gateway6: Option<String>,
1116
1117 #[serde(skip_serializing_if="Vec::is_empty")]
1118 pub options: Vec<String>,
1119 #[serde(skip_serializing_if="Vec::is_empty")]
1120 pub options6: Vec<String>,
1121
1122 #[serde(skip_serializing_if="Option::is_none")]
1123 pub comments: Option<String>,
1124 #[serde(skip_serializing_if="Option::is_none")]
1125 pub comments6: Option<String>,
1126
1127 #[serde(skip_serializing_if="Option::is_none")]
1128 /// Maximum Transmission Unit
1129 pub mtu: Option<u64>,
1130
1131 #[serde(skip_serializing_if="Option::is_none")]
1132 pub bridge_ports: Option<Vec<String>>,
1133 /// Enable bridge vlan support.
1134 #[serde(skip_serializing_if="Option::is_none")]
1135 pub bridge_vlan_aware: Option<bool>,
1136
1137 #[serde(skip_serializing_if="Option::is_none")]
1138 pub slaves: Option<Vec<String>>,
1139 #[serde(skip_serializing_if="Option::is_none")]
1140 pub bond_mode: Option<LinuxBondMode>,
1141 #[serde(skip_serializing_if="Option::is_none")]
1142 #[serde(rename = "bond-primary")]
1143 pub bond_primary: Option<String>,
1144 pub bond_xmit_hash_policy: Option<BondXmitHashPolicy>,
1145 }
1146
1147 // Regression tests
1148
1149 #[test]
1150 fn test_cert_fingerprint_schema() -> Result<(), anyhow::Error> {
1151
1152 let schema = CERT_FINGERPRINT_SHA256_SCHEMA;
1153
1154 let invalid_fingerprints = [
1155 "86:88:7c:be:26:77:a5:62:67:d9:06:f5:e4::61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8",
1156 "88:7C:BE:26:77:a5:62:67:D9:06:f5:e4:14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8",
1157 "86:88:7c:be:26:77:a5:62:67:d9:06:f5:e4::14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8:ff",
1158 "XX:88:7c:be:26:77:a5:62:67:d9:06:f5:e4::14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8",
1159 "86:88:Y4:be:26:77:a5:62:67:d9:06:f5:e4:14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8",
1160 "86:88:0:be:26:77:a5:62:67:d9:06:f5:e4:14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8",
1161 ];
1162
1163 for fingerprint in invalid_fingerprints.iter() {
1164 if parse_simple_value(fingerprint, &schema).is_ok() {
1165 bail!("test fingerprint '{}' failed - got Ok() while exception an error.", fingerprint);
1166 }
1167 }
1168
1169 let valid_fingerprints = [
1170 "86:88:7c:be:26:77:a5:62:67:d9:06:f5:e4:14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8",
1171 "86:88:7C:BE:26:77:a5:62:67:D9:06:f5:e4:14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8",
1172 ];
1173
1174 for fingerprint in valid_fingerprints.iter() {
1175 let v = match parse_simple_value(fingerprint, &schema) {
1176 Ok(v) => v,
1177 Err(err) => {
1178 bail!("unable to parse fingerprint '{}' - {}", fingerprint, err);
1179 }
1180 };
1181
1182 if v != serde_json::json!(fingerprint) {
1183 bail!("unable to parse fingerprint '{}' - got wrong value {:?}", fingerprint, v);
1184 }
1185 }
1186
1187 Ok(())
1188 }
1189
1190 #[test]
1191 fn test_proxmox_user_id_schema() -> Result<(), anyhow::Error> {
1192 let invalid_user_ids = [
1193 "x", // too short
1194 "xx", // too short
1195 "xxx", // no realm
1196 "xxx@", // no realm
1197 "xx x@test", // contains space
1198 "xx\nx@test", // contains control character
1199 "x:xx@test", // contains collon
1200 "xx/x@test", // contains slash
1201 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@test", // too long
1202 ];
1203
1204 for name in invalid_user_ids.iter() {
1205 if parse_simple_value(name, &Userid::API_SCHEMA).is_ok() {
1206 bail!("test userid '{}' failed - got Ok() while exception an error.", name);
1207 }
1208 }
1209
1210 let valid_user_ids = [
1211 "xxx@y",
1212 "name@y",
1213 "xxx@test-it.com",
1214 "xxx@_T_E_S_T-it.com",
1215 "x_x-x.x@test-it.com",
1216 ];
1217
1218 for name in valid_user_ids.iter() {
1219 let v = match parse_simple_value(name, &Userid::API_SCHEMA) {
1220 Ok(v) => v,
1221 Err(err) => {
1222 bail!("unable to parse userid '{}' - {}", name, err);
1223 }
1224 };
1225
1226 if v != serde_json::json!(name) {
1227 bail!("unable to parse userid '{}' - got wrong value {:?}", name, v);
1228 }
1229 }
1230
1231 Ok(())
1232 }
1233
1234 #[api()]
1235 #[derive(Copy, Clone, Serialize, Deserialize)]
1236 #[serde(rename_all = "UPPERCASE")]
1237 pub enum RRDMode {
1238 /// Maximum
1239 Max,
1240 /// Average
1241 Average,
1242 }
1243
1244
1245 #[api()]
1246 #[repr(u64)]
1247 #[derive(Copy, Clone, Serialize, Deserialize)]
1248 #[serde(rename_all = "lowercase")]
1249 pub enum RRDTimeFrameResolution {
1250 /// 1 min => last 70 minutes
1251 Hour = 60,
1252 /// 30 min => last 35 hours
1253 Day = 60*30,
1254 /// 3 hours => about 8 days
1255 Week = 60*180,
1256 /// 12 hours => last 35 days
1257 Month = 60*720,
1258 /// 1 week => last 490 days
1259 Year = 60*10080,
1260 }
1261
1262 #[api()]
1263 #[derive(Debug, Clone, Serialize, Deserialize)]
1264 #[serde(rename_all = "PascalCase")]
1265 /// Describes a package for which an update is available.
1266 pub struct APTUpdateInfo {
1267 /// Package name
1268 pub package: String,
1269 /// Package title
1270 pub title: String,
1271 /// Package architecture
1272 pub arch: String,
1273 /// Human readable package description
1274 pub description: String,
1275 /// New version to be updated to
1276 pub version: String,
1277 /// Old version currently installed
1278 pub old_version: String,
1279 /// Package origin
1280 pub origin: String,
1281 /// Package priority in human-readable form
1282 pub priority: String,
1283 /// Package section
1284 pub section: String,
1285 /// URL under which the package's changelog can be retrieved
1286 pub change_log_url: String,
1287 /// Custom extra field for additional package information
1288 #[serde(skip_serializing_if="Option::is_none")]
1289 pub extra_info: Option<String>,
1290 }
1291
1292 #[api()]
1293 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
1294 #[serde(rename_all = "lowercase")]
1295 /// When do we send notifications
1296 pub enum Notify {
1297 /// Never send notification
1298 Never,
1299 /// Send notifications for failed and successful jobs
1300 Always,
1301 /// Send notifications for failed jobs only
1302 Error,
1303 }
1304
1305 #[api(
1306 properties: {
1307 gc: {
1308 type: Notify,
1309 optional: true,
1310 },
1311 verify: {
1312 type: Notify,
1313 optional: true,
1314 },
1315 sync: {
1316 type: Notify,
1317 optional: true,
1318 },
1319 },
1320 )]
1321 #[derive(Debug, Serialize, Deserialize)]
1322 /// Datastore notify settings
1323 pub struct DatastoreNotify {
1324 /// Garbage collection settings
1325 pub gc: Option<Notify>,
1326 /// Verify job setting
1327 pub verify: Option<Notify>,
1328 /// Sync job setting
1329 pub sync: Option<Notify>,
1330 }
1331
1332 /// An entry in a hierarchy of files for restore and listing.
1333 #[api()]
1334 #[derive(Serialize, Deserialize)]
1335 pub struct ArchiveEntry {
1336 /// Base64-encoded full path to the file, including the filename
1337 pub filepath: String,
1338 /// Displayable filename text for UIs
1339 pub text: String,
1340 /// File or directory type of this entry
1341 #[serde(rename = "type")]
1342 pub entry_type: String,
1343 /// Is this entry a leaf node, or does it have children (i.e. a directory)?
1344 pub leaf: bool,
1345 /// The file size, if entry_type is 'f' (file)
1346 #[serde(skip_serializing_if="Option::is_none")]
1347 pub size: Option<u64>,
1348 /// The file "last modified" time stamp, if entry_type is 'f' (file)
1349 #[serde(skip_serializing_if="Option::is_none")]
1350 pub mtime: Option<i64>,
1351 }
1352
1353 impl ArchiveEntry {
1354 pub fn new(filepath: &[u8], entry_type: &DirEntryAttribute) -> Self {
1355 Self {
1356 filepath: base64::encode(filepath),
1357 text: String::from_utf8_lossy(filepath.split(|x| *x == b'/').last().unwrap())
1358 .to_string(),
1359 entry_type: CatalogEntryType::from(entry_type).to_string(),
1360 leaf: !matches!(entry_type, DirEntryAttribute::Directory { .. }),
1361 size: match entry_type {
1362 DirEntryAttribute::File { size, .. } => Some(*size),
1363 _ => None
1364 },
1365 mtime: match entry_type {
1366 DirEntryAttribute::File { mtime, .. } => Some(*mtime),
1367 _ => None
1368 },
1369 }
1370 }
1371 }
1372
1373 pub const DATASTORE_NOTIFY_STRING_SCHEMA: Schema = StringSchema::new(
1374 "Datastore notification setting")
1375 .format(&ApiStringFormat::PropertyString(&DatastoreNotify::API_SCHEMA))
1376 .schema();
1377
1378
1379 pub const PASSWORD_HINT_SCHEMA: Schema = StringSchema::new("Password hint.")
1380 .format(&SINGLE_LINE_COMMENT_FORMAT)
1381 .min_length(1)
1382 .max_length(64)
1383 .schema();
1384
1385 #[api(default: "scrypt")]
1386 #[derive(Clone, Copy, Debug, Deserialize, Serialize)]
1387 #[serde(rename_all = "lowercase")]
1388 /// Key derivation function for password protected encryption keys.
1389 pub enum Kdf {
1390 /// Do not encrypt the key.
1391 None,
1392 /// Encrypt they key with a password using SCrypt.
1393 Scrypt,
1394 /// Encrtypt the Key with a password using PBKDF2
1395 PBKDF2,
1396 }
1397
1398 impl Default for Kdf {
1399 #[inline]
1400 fn default() -> Self {
1401 Kdf::Scrypt
1402 }
1403 }
1404
1405 #[api(
1406 properties: {
1407 kdf: {
1408 type: Kdf,
1409 },
1410 fingerprint: {
1411 schema: CERT_FINGERPRINT_SHA256_SCHEMA,
1412 optional: true,
1413 },
1414 },
1415 )]
1416 #[derive(Deserialize, Serialize)]
1417 /// Encryption Key Information
1418 pub struct KeyInfo {
1419 /// Path to key (if stored in a file)
1420 #[serde(skip_serializing_if="Option::is_none")]
1421 pub path: Option<String>,
1422 pub kdf: Kdf,
1423 /// Key creation time
1424 pub created: i64,
1425 /// Key modification time
1426 pub modified: i64,
1427 /// Key fingerprint
1428 #[serde(skip_serializing_if="Option::is_none")]
1429 pub fingerprint: Option<String>,
1430 /// Password hint
1431 #[serde(skip_serializing_if="Option::is_none")]
1432 pub hint: Option<String>,
1433 }
1434
1435 #[api]
1436 #[derive(Deserialize, Serialize)]
1437 /// RSA public key information
1438 pub struct RsaPubKeyInfo {
1439 /// Path to key (if stored in a file)
1440 #[serde(skip_serializing_if="Option::is_none")]
1441 pub path: Option<String>,
1442 /// RSA exponent
1443 pub exponent: String,
1444 /// Hex-encoded RSA modulus
1445 pub modulus: String,
1446 /// Key (modulus) length in bits
1447 pub length: usize,
1448 }
1449
1450 impl std::convert::TryFrom<openssl::rsa::Rsa<openssl::pkey::Public>> for RsaPubKeyInfo {
1451 type Error = anyhow::Error;
1452
1453 fn try_from(value: openssl::rsa::Rsa<openssl::pkey::Public>) -> Result<Self, Self::Error> {
1454 let modulus = value.n().to_hex_str()?.to_string();
1455 let exponent = value.e().to_dec_str()?.to_string();
1456 let length = value.size() as usize * 8;
1457
1458 Ok(Self {
1459 path: None,
1460 exponent,
1461 modulus,
1462 length,
1463 })
1464 }
1465 }
1466
1467 #[api(
1468 properties: {
1469 "next-run": {
1470 description: "Estimated time of the next run (UNIX epoch).",
1471 optional: true,
1472 type: Integer,
1473 },
1474 "last-run-state": {
1475 description: "Result of the last run.",
1476 optional: true,
1477 type: String,
1478 },
1479 "last-run-upid": {
1480 description: "Task UPID of the last run.",
1481 optional: true,
1482 type: String,
1483 },
1484 "last-run-endtime": {
1485 description: "Endtime of the last run.",
1486 optional: true,
1487 type: Integer,
1488 },
1489 }
1490 )]
1491 #[serde(rename_all="kebab-case")]
1492 #[derive(Serialize,Deserialize,Default)]
1493 /// Job Scheduling Status
1494 pub struct JobScheduleStatus {
1495 #[serde(skip_serializing_if="Option::is_none")]
1496 pub next_run: Option<i64>,
1497 #[serde(skip_serializing_if="Option::is_none")]
1498 pub last_run_state: Option<String>,
1499 #[serde(skip_serializing_if="Option::is_none")]
1500 pub last_run_upid: Option<String>,
1501 #[serde(skip_serializing_if="Option::is_none")]
1502 pub last_run_endtime: Option<i64>,
1503 }