1use garde::Validate;
2use serde::{Deserialize, Serialize};
3use utoipa::ToSchema;
4
5pub type Config = super::system_config::get::Response200;
6
7#[derive(Debug, ToSchema, Deserialize, Serialize, Clone)]
8#[serde(tag = "type", rename_all = "snake_case")]
9pub enum QueryValue {
10 Null,
11 Text { value: String },
12 Binary { value: String },
13}
14
15#[derive(Debug, ToSchema, Deserialize, Serialize, Clone)]
16#[serde(tag = "type", rename_all = "snake_case")]
17#[non_exhaustive]
18pub enum ServerSelector {
19 Uuids {
20 uuids: std::collections::HashSet<uuid::Uuid>,
21 },
22 All,
23}
24
25impl std::fmt::Display for super::StreamableArchiveFormat {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 write!(
28 f,
29 "{}",
30 match self {
31 super::StreamableArchiveFormat::Tar => "tar",
32 super::StreamableArchiveFormat::TarGz => "tar_gz",
33 super::StreamableArchiveFormat::TarXz => "tar_xz",
34 super::StreamableArchiveFormat::TarLzip => "tar_lzip",
35 super::StreamableArchiveFormat::TarBz2 => "tar_bz2",
36 super::StreamableArchiveFormat::TarLz4 => "tar_lz4",
37 super::StreamableArchiveFormat::TarZstd => "tar_zstd",
38 super::StreamableArchiveFormat::Itaf => "itaf",
39 super::StreamableArchiveFormat::ItafGz => "itaf_gz",
40 super::StreamableArchiveFormat::ItafXz => "itaf_xz",
41 super::StreamableArchiveFormat::ItafLzip => "itaf_lzip",
42 super::StreamableArchiveFormat::ItafBz2 => "itaf_bz2",
43 super::StreamableArchiveFormat::ItafLz4 => "itaf_lz4",
44 super::StreamableArchiveFormat::ItafZstd => "itaf_zstd",
45 super::StreamableArchiveFormat::Zip => "zip",
46 }
47 )
48 }
49}
50
51#[allow(clippy::derivable_impls)]
52impl Default for super::StreamableArchiveFormat {
53 #[inline]
54 fn default() -> Self {
55 super::StreamableArchiveFormat::TarGz
56 }
57}
58
59impl std::fmt::Display for super::BackupAdapter {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 write!(
62 f,
63 "{}",
64 match self {
65 super::BackupAdapter::Wings => "wings",
66 super::BackupAdapter::S3 => "s3",
67 super::BackupAdapter::DdupBak => "ddup-bak",
68 super::BackupAdapter::Btrfs => "btrfs",
69 super::BackupAdapter::Zfs => "zfs",
70 super::BackupAdapter::Restic => "restic",
71 super::BackupAdapter::ProxmoxBackupServer => "proxmox-backup-server",
72 super::BackupAdapter::Kopia => "kopia",
73 }
74 )
75 }
76}
77
78#[derive(Debug, ToSchema, Deserialize, Serialize, Clone, Copy)]
79#[serde(rename_all = "lowercase")]
80#[non_exhaustive]
81pub enum Algorithm {
82 Md5,
83 Crc32,
84 Sha1,
85 Sha224,
86 Sha256,
87 Sha384,
88 Sha512,
89 Curseforge,
90}
91
92impl std::fmt::Display for Algorithm {
93 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 write!(
95 f,
96 "{}",
97 match self {
98 Algorithm::Md5 => "md5",
99 Algorithm::Crc32 => "crc32",
100 Algorithm::Sha1 => "sha1",
101 Algorithm::Sha224 => "sha224",
102 Algorithm::Sha256 => "sha256",
103 Algorithm::Sha384 => "sha384",
104 Algorithm::Sha512 => "sha512",
105 Algorithm::Curseforge => "curseforge",
106 }
107 )
108 }
109}
110
111#[derive(Debug, Default, ToSchema, Deserialize, Serialize, Clone, Copy)]
112#[serde(rename_all = "snake_case")]
113#[non_exhaustive]
114pub enum DirectorySortingMode {
115 #[default]
116 NameAsc,
117 NameDesc,
118 SizeAsc,
119 SizeDesc,
120 PhysicalSizeAsc,
121 PhysicalSizeDesc,
122 ModifiedAsc,
123 ModifiedDesc,
124 CreatedAsc,
125 CreatedDesc,
126}
127
128impl std::fmt::Display for DirectorySortingMode {
129 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130 write!(
131 f,
132 "{}",
133 match self {
134 DirectorySortingMode::NameAsc => "name_asc",
135 DirectorySortingMode::NameDesc => "name_desc",
136 DirectorySortingMode::SizeAsc => "size_asc",
137 DirectorySortingMode::SizeDesc => "size_desc",
138 DirectorySortingMode::PhysicalSizeAsc => "physical_size_asc",
139 DirectorySortingMode::PhysicalSizeDesc => "physical_size_desc",
140 DirectorySortingMode::ModifiedAsc => "modified_asc",
141 DirectorySortingMode::ModifiedDesc => "modified_desc",
142 DirectorySortingMode::CreatedAsc => "created_asc",
143 DirectorySortingMode::CreatedDesc => "created_desc",
144 }
145 )
146 }
147}
148
149#[derive(ToSchema, Debug, Clone, Copy, Deserialize, Serialize)]
150#[serde(rename_all = "lowercase")]
151pub enum ServerBackupStatus {
152 Starting,
153 Finished,
154 Failed,
155}
156
157#[derive(Debug, ToSchema, Deserialize, Serialize, Clone, Copy)]
158#[serde(rename_all = "snake_case")]
159#[non_exhaustive]
160pub enum Game {
161 MinecraftJava,
162}
163
164impl std::fmt::Display for Game {
165 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166 write!(
167 f,
168 "{}",
169 match self {
170 Game::MinecraftJava => "minecraft_java",
171 }
172 )
173 }
174}
175
176#[derive(ToSchema, Clone, Deserialize, Serialize, Validate)]
177pub struct ScheduleVariable {
178 #[garde(length(chars, min = 1, max = 255))]
179 #[schema(min_length = 1, max_length = 255)]
180 pub variable: compact_str::CompactString,
181}
182
183#[derive(ToSchema, Clone, Deserialize, Serialize, Validate)]
184#[serde(untagged)]
185pub enum ScheduleDynamicParameter {
186 Raw(#[garde(length(chars, min = 1, max = 16384))] compact_str::CompactString),
187 Variable(#[garde(dive)] ScheduleVariable),
188}
189
190#[derive(ToSchema, Deserialize, Serialize, Clone, Copy)]
191#[serde(rename_all = "snake_case")]
192pub enum ScheduleHttpMethod {
193 Get,
194 Post,
195 Put,
196 Patch,
197 Delete,
198 Head,
199}
200
201#[derive(ToSchema, Clone, Deserialize, Serialize, Validate)]
202pub struct ScheduleHttpHeader {
203 #[garde(length(chars, min = 1, max = 128))]
204 #[schema(min_length = 1, max_length = 128)]
205 pub name: compact_str::CompactString,
206 #[garde(dive)]
207 pub value: ScheduleDynamicParameter,
208}
209
210#[derive(ToSchema, Deserialize, Serialize)]
211pub struct ScheduleAction {
212 pub uuid: uuid::Uuid,
213
214 #[serde(flatten)]
215 pub inner: ScheduleActionInner,
216}
217
218#[derive(ToSchema, Validate, Deserialize, Serialize, Clone)]
219#[serde(rename_all = "snake_case", tag = "mode")]
220#[non_exhaustive]
221pub enum ScheduleBackupSelector {
222 Latest {
223 #[garde(skip)]
224 #[serde(default)]
225 backup_group_uuid: Option<uuid::Uuid>,
226 },
227 Oldest {
228 #[garde(skip)]
229 #[serde(default)]
230 backup_group_uuid: Option<uuid::Uuid>,
231 },
232 Uuid {
233 #[garde(dive)]
234 uuid: ScheduleDynamicParameter,
235 },
236 Name {
237 #[garde(dive)]
238 name: ScheduleDynamicParameter,
239 #[garde(skip)]
240 #[serde(default)]
241 backup_group_uuid: Option<uuid::Uuid>,
242 #[garde(skip)]
243 #[serde(default)]
244 oldest: bool,
245 },
246}
247
248#[derive(ToSchema, Validate, Deserialize, Serialize, Clone)]
249#[serde(rename_all = "snake_case", tag = "type")]
250#[non_exhaustive]
251pub enum ScheduleActionInner {
252 Sleep {
253 #[garde(range(min = 1, max = 24 * 60 * 60 * 1000))]
254 #[schema(minimum = 1, maximum = 86400000)]
255 duration: u64,
256 },
257 Ensure {
258 #[garde(dive, custom(ScheduleCondition::validate_nesting))]
259 condition: ScheduleCondition,
260 },
261 If {
262 #[garde(dive, custom(ScheduleCondition::validate_nesting))]
263 condition: ScheduleCondition,
264 },
265 ElseIf {
266 #[garde(dive, custom(ScheduleCondition::validate_nesting))]
267 condition: ScheduleCondition,
268 },
269 Else,
270 EndIf,
271 Exit {
272 #[garde(skip)]
273 successful: bool,
274 },
275 WaitForState {
276 #[garde(skip)]
277 ignore_failure: bool,
278
279 #[garde(skip)]
280 state: super::ServerState,
281 #[garde(range(min = 1, max = 24 * 60 * 60 * 1000))]
282 #[schema(minimum = 1, maximum = 86400000)]
283 timeout: u64,
284 },
285 Format {
286 #[garde(length(chars, min = 1, max = 16384))]
287 #[schema(min_length = 1, max_length = 16384)]
288 format: String,
289 #[garde(dive)]
290 output_into: ScheduleVariable,
291 },
292 MatchRegex {
293 #[garde(dive)]
294 input: ScheduleDynamicParameter,
295
296 #[garde(skip)]
297 #[serde(with = "serde_regex")]
298 #[schema(value_type = String, format = "regex")]
299 regex: regex::Regex,
300
301 #[garde(skip)]
302 output_into: Vec<Option<ScheduleVariable>>,
303 },
304 WaitForConsoleLine {
305 #[garde(skip)]
306 ignore_failure: bool,
307
308 #[garde(dive)]
309 contains: ScheduleDynamicParameter,
310 #[garde(skip)]
311 #[serde(default)]
312 case_insensitive: bool,
313 #[garde(range(min = 1, max = 24 * 60 * 60 * 1000))]
314 #[schema(minimum = 1, maximum = 86400000)]
315 timeout: u64,
316
317 #[garde(dive)]
318 output_into: Option<ScheduleVariable>,
319 },
320 SendPower {
321 #[garde(skip)]
322 ignore_failure: bool,
323
324 #[garde(skip)]
325 action: super::ServerPowerAction,
326 },
327 SendCommand {
328 #[garde(skip)]
329 ignore_failure: bool,
330
331 #[garde(dive)]
332 command: ScheduleDynamicParameter,
333 },
334 CreateBackup {
335 #[garde(skip)]
336 ignore_failure: bool,
337 #[garde(skip)]
338 foreground: bool,
339
340 #[garde(dive)]
341 name: Option<ScheduleDynamicParameter>,
342 #[garde(skip)]
343 #[serde(default)]
344 backup_group_uuid: Option<uuid::Uuid>,
345 #[garde(skip)]
346 ignored_files: Vec<compact_str::CompactString>,
347
348 #[garde(dive)]
349 #[serde(default)]
350 output_into: Option<ScheduleVariable>,
351 },
352 RestoreBackup {
353 #[garde(skip)]
354 ignore_failure: bool,
355 #[garde(skip)]
356 truncate_directory: bool,
357 #[garde(skip)]
358 #[serde(default)]
359 restore_startup: bool,
360
361 #[garde(dive)]
362 backup: ScheduleBackupSelector,
363 },
364 DeleteBackup {
365 #[garde(skip)]
366 #[serde(default)]
367 ignore_failure: bool,
368
369 #[garde(dive)]
370 backup: ScheduleBackupSelector,
371 },
372 MoveBackup {
373 #[garde(skip)]
374 #[serde(default)]
375 ignore_failure: bool,
376
377 #[garde(dive)]
378 backup: ScheduleBackupSelector,
379 #[garde(skip)]
380 #[serde(default)]
381 backup_group_uuid: Option<uuid::Uuid>,
382 },
383 CreateDirectory {
384 #[garde(skip)]
385 ignore_failure: bool,
386
387 #[garde(dive)]
388 root: ScheduleDynamicParameter,
389 #[garde(dive)]
390 name: ScheduleDynamicParameter,
391 },
392 WriteFile {
393 #[garde(skip)]
394 ignore_failure: bool,
395 #[garde(skip)]
396 append: bool,
397
398 #[garde(dive)]
399 file: ScheduleDynamicParameter,
400 #[garde(dive)]
401 content: ScheduleDynamicParameter,
402 },
403 CopyFile {
404 #[garde(skip)]
405 ignore_failure: bool,
406 #[garde(skip)]
407 foreground: bool,
408
409 #[garde(dive)]
410 file: ScheduleDynamicParameter,
411 #[garde(dive)]
412 destination: ScheduleDynamicParameter,
413 },
414 DeleteFiles {
415 #[garde(skip)]
416 #[serde(default)]
417 ignore_failure: bool,
418
419 #[garde(dive)]
420 root: ScheduleDynamicParameter,
421 #[garde(skip)]
422 files: Vec<compact_str::CompactString>,
423 },
424 RenameFiles {
425 #[garde(skip)]
426 #[serde(default)]
427 ignore_failure: bool,
428
429 #[garde(dive)]
430 root: ScheduleDynamicParameter,
431 #[garde(skip)]
432 #[schema(inline)]
433 files: Vec<super::servers_server_files_rename::put::RequestBodyFiles>,
434 },
435 CompressFiles {
436 #[garde(skip)]
437 ignore_failure: bool,
438 #[garde(skip)]
439 foreground: bool,
440
441 #[garde(dive)]
442 root: ScheduleDynamicParameter,
443 #[garde(skip)]
444 files: Vec<compact_str::CompactString>,
445 #[garde(skip)]
446 format: super::ArchiveFormat,
447 #[garde(dive)]
448 name: ScheduleDynamicParameter,
449 },
450 DecompressFile {
451 #[garde(skip)]
452 ignore_failure: bool,
453 #[garde(skip)]
454 foreground: bool,
455
456 #[garde(dive)]
457 root: ScheduleDynamicParameter,
458 #[garde(dive)]
459 file: ScheduleDynamicParameter,
460 },
461 UpdateStartupVariable {
462 #[garde(skip)]
463 ignore_failure: bool,
464
465 #[garde(dive)]
466 env_variable: ScheduleDynamicParameter,
467 #[garde(dive)]
468 value: ScheduleDynamicParameter,
469 },
470 UpdateStartupCommand {
471 #[garde(skip)]
472 ignore_failure: bool,
473
474 #[garde(dive)]
475 command: ScheduleDynamicParameter,
476 },
477 UpdateStartupDockerImage {
478 #[garde(skip)]
479 ignore_failure: bool,
480
481 #[garde(dive)]
482 image: ScheduleDynamicParameter,
483 },
484 HttpRequest {
485 #[garde(skip)]
486 ignore_failure: bool,
487
488 #[garde(skip)]
489 method: ScheduleHttpMethod,
490 #[garde(skip)]
491 #[schema(value_type = String, format = "uri")]
492 url: reqwest::Url,
493 #[garde(length(max = 32), dive)]
494 #[serde(default)]
495 headers: Vec<ScheduleHttpHeader>,
496 #[garde(dive)]
497 #[serde(default)]
498 body: Option<ScheduleDynamicParameter>,
499 #[garde(range(min = 1, max = 60 * 1000))]
500 #[schema(minimum = 1, maximum = 60000)]
501 timeout: u64,
502 #[garde(skip)]
503 #[serde(default)]
504 ignore_error_status: bool,
505
506 #[garde(dive)]
507 #[serde(default)]
508 output_status_into: Option<ScheduleVariable>,
509 #[garde(dive)]
510 #[serde(default)]
511 output_body_into: Option<ScheduleVariable>,
512 },
513}
514
515impl ScheduleActionInner {
516 pub fn permission(&self) -> Option<&'static str> {
517 match self {
518 ScheduleActionInner::Sleep { .. } => None,
519 ScheduleActionInner::Ensure { .. } => None,
520 ScheduleActionInner::If { .. } => None,
521 ScheduleActionInner::ElseIf { .. } => None,
522 ScheduleActionInner::Else => None,
523 ScheduleActionInner::EndIf => None,
524 ScheduleActionInner::Exit { .. } => None,
525 ScheduleActionInner::WaitForState { .. } => None,
526 ScheduleActionInner::Format { .. } => None,
527 ScheduleActionInner::MatchRegex { .. } => None,
528 ScheduleActionInner::WaitForConsoleLine { .. } => Some("control.read-console"),
529 ScheduleActionInner::SendPower { action, .. } => match action {
530 super::ServerPowerAction::Start => Some("control.start"),
531 super::ServerPowerAction::Stop => Some("control.stop"),
532 super::ServerPowerAction::Restart => Some("control.restart"),
533 super::ServerPowerAction::Kill => Some("control.stop"),
534 },
535 ScheduleActionInner::SendCommand { .. } => Some("control.console"),
536 ScheduleActionInner::CreateBackup { .. } => Some("backups.create"),
537 ScheduleActionInner::RestoreBackup { .. } => Some("backups.restore"),
538 ScheduleActionInner::DeleteBackup { .. } => Some("backups.delete"),
539 ScheduleActionInner::MoveBackup { .. } => Some("backups.update"),
540 ScheduleActionInner::CreateDirectory { .. } => Some("files.create"),
541 ScheduleActionInner::WriteFile { .. } => Some("files.update"),
542 ScheduleActionInner::CopyFile { .. } => Some("files.update"),
543 ScheduleActionInner::DeleteFiles { .. } => Some("files.delete"),
544 ScheduleActionInner::RenameFiles { .. } => Some("files.update"),
545 ScheduleActionInner::CompressFiles { .. } => Some("files.archive"),
546 ScheduleActionInner::DecompressFile { .. } => Some("files.archive"),
547 ScheduleActionInner::UpdateStartupVariable { .. } => Some("startup.update"),
548 ScheduleActionInner::UpdateStartupCommand { .. } => Some("startup.command"),
549 ScheduleActionInner::UpdateStartupDockerImage { .. } => Some("startup.docker-image"),
550 ScheduleActionInner::HttpRequest { .. } => None,
551 }
552 }
553}
554
555#[derive(ToSchema, Validate, Deserialize, Serialize, Clone)]
556#[serde(rename_all = "snake_case", tag = "type")]
557#[non_exhaustive]
558pub enum ScheduleTrigger {
559 Cron {
560 #[garde(skip)]
561 #[schema(value_type = String, example = "* * * * * *")]
562 schedule: Box<croner::Cron>,
563 },
564 PowerAction {
565 #[garde(skip)]
566 action: super::ServerPowerAction,
567 },
568 ServerState {
569 #[garde(skip)]
570 state: super::ServerState,
571 },
572 BackupStatus {
573 #[garde(skip)]
574 status: ServerBackupStatus,
575 },
576 ScheduleCompletion {
577 #[garde(skip)]
578 schedule: uuid::Uuid,
579 #[garde(skip)]
580 successful: bool,
581 },
582 ResourceUsage {
583 #[garde(skip)]
584 metric: ScheduleResourceMetric,
585 #[garde(skip)]
586 comparator: ScheduleConditionComparator,
587 #[garde(range(min = 0.0))]
588 value: f64,
589 #[garde(range(min = 0, max = 24 * 60 * 60))]
590 #[schema(minimum = 0, maximum = 86400)]
591 #[serde(default)]
592 for_seconds: u64,
593 },
594 ConsoleLine {
595 #[garde(length(chars, min = 1, max = 1024))]
596 #[schema(min_length = 1, max_length = 1024)]
597 contains: compact_str::CompactString,
598 #[garde(skip)]
599 #[serde(default)]
600 case_insensitive: bool,
601 #[garde(dive)]
602 output_into: Option<ScheduleVariable>,
603 },
604 Crash,
605}
606
607#[derive(ToSchema, Deserialize, Serialize, Clone, Copy)]
608#[serde(rename_all = "snake_case")]
609pub enum ScheduleConditionComparator {
610 SmallerThan,
611 SmallerThanOrEquals,
612 Equal,
613 GreaterThan,
614 GreaterThanOrEquals,
615}
616
617#[derive(ToSchema, Deserialize, Serialize, Clone, Copy)]
618#[serde(rename_all = "snake_case")]
619pub enum ScheduleResourceMetric {
620 Cpu,
621 Memory,
622 Disk,
623}
624
625#[derive(ToSchema, Deserialize, Serialize, Clone, Validate)]
626#[serde(rename_all = "snake_case", tag = "type")]
627#[schema(no_recursion)]
628pub enum ScheduleCondition {
629 None,
630 And {
631 #[garde(dive)]
632 conditions: Vec<ScheduleCondition>,
633 },
634 Or {
635 #[garde(dive)]
636 conditions: Vec<ScheduleCondition>,
637 },
638 Not {
639 #[garde(dive)]
640 condition: Box<ScheduleCondition>,
641 },
642 ServerState {
643 #[garde(skip)]
644 state: super::ServerState,
645 },
646 Uptime {
647 #[garde(skip)]
648 comparator: ScheduleConditionComparator,
649 #[garde(skip)]
650 value: u64,
651 },
652 ResourceUsage {
653 #[garde(skip)]
654 metric: ScheduleResourceMetric,
655 #[garde(skip)]
656 comparator: ScheduleConditionComparator,
657 #[garde(range(min = 0.0))]
658 value: f64,
659 },
660 FileExists {
661 #[garde(length(chars, min = 1, max = 255))]
662 #[schema(min_length = 1, max_length = 255)]
663 file: String,
664 },
665 VariableExists {
666 #[garde(dive)]
667 variable: ScheduleVariable,
668 },
669 VariableEquals {
670 #[garde(dive)]
671 variable: ScheduleVariable,
672 #[garde(dive)]
673 equals: ScheduleDynamicParameter,
674 },
675 VariableContains {
676 #[garde(dive)]
677 variable: ScheduleVariable,
678 #[garde(dive)]
679 contains: ScheduleDynamicParameter,
680 },
681 VariableStartsWith {
682 #[garde(dive)]
683 variable: ScheduleVariable,
684 #[garde(dive)]
685 starts_with: ScheduleDynamicParameter,
686 },
687 VariableEndsWith {
688 #[garde(dive)]
689 variable: ScheduleVariable,
690 #[garde(dive)]
691 ends_with: ScheduleDynamicParameter,
692 },
693}
694
695impl ScheduleCondition {
696 pub const MAX_NESTING_DEPTH: usize = 3;
697
698 fn nested_within_limit(&self, depth: usize) -> bool {
699 match self {
700 ScheduleCondition::And { conditions } | ScheduleCondition::Or { conditions } => {
701 depth < Self::MAX_NESTING_DEPTH
702 && conditions.iter().all(|c| c.nested_within_limit(depth + 1))
703 }
704 ScheduleCondition::Not { condition } => {
705 depth < Self::MAX_NESTING_DEPTH && condition.nested_within_limit(depth + 1)
706 }
707 _ => true,
708 }
709 }
710
711 pub fn validate_nesting(value: &Self, _context: &()) -> garde::Result {
712 if value.nested_within_limit(0) {
713 Ok(())
714 } else {
715 Err(garde::Error::new(format!(
716 "condition may not nest groups more than {} levels deep",
717 Self::MAX_NESTING_DEPTH
718 )))
719 }
720 }
721
722 pub fn validate_optional_nesting(value: &Option<Self>, context: &()) -> garde::Result {
723 match value {
724 Some(condition) => Self::validate_nesting(condition, context),
725 None => Ok(()),
726 }
727 }
728}
729
730const FORBIDDEN_CONFIG_PATHS: &[&str] = &[
732 "uuid",
733 "token",
734 "token_id",
735 "remote",
736 "remote_headers",
737 "system.root_directory",
738 "system.log_directory",
739 "system.data",
740 "system.diffs_directory",
741 "system.vmount_directory",
742 "system.archive_directory",
743 "system.backup_directory",
744 "system.tmp_directory",
745 "system.passwd.directory",
746 "system.backups.restic.repository",
747 "system.backups.restic.password_file",
748 "system.backups.mounting.path",
749 "system.username",
750 "system.user",
751 "system.passwd",
752 "docker.socket",
753 "allowed_mounts",
754 "ignore_panel_config_updates",
755 "ignore_panel_wings_upgrades",
756 "api.host",
757 "api.port",
758 "api.ssl",
759 "api.trusted_proxies",
760 "api.disable_remote_download",
761 "api.remote_download_blocked_cidrs",
762 "api.schedule.steps.http_request",
763];
764
765pub fn strip_config_paths(value: &mut serde_json::Value) {
766 for path in FORBIDDEN_CONFIG_PATHS {
767 let mut cursor = &mut *value;
768 let mut parts = path.split('.').peekable();
769
770 while let Some(part) = parts.next() {
771 let serde_json::Value::Object(map) = cursor else {
772 break;
773 };
774
775 if parts.peek().is_none() {
776 map.remove(part);
777 break;
778 }
779
780 if map.get(part).is_some_and(|next| !next.is_object()) {
781 map.remove(part);
782 break;
783 }
784
785 match map.get_mut(part) {
786 Some(next) => cursor = next,
787 None => break,
788 }
789 }
790 }
791}