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