]> git.proxmox.com Git - pve-cluster.git/blame - src/PVE/DataCenterConfig.pm
bump version to 8.0.6
[pve-cluster.git] / src / PVE / DataCenterConfig.pm
CommitLineData
ab966729
FG
1package PVE::DataCenterConfig;
2
3use strict;
4use warnings;
5
731f4e15 6use PVE::JSONSchema qw(get_standard_option parse_property_string register_standard_option);
ab966729
FG
7use PVE::Tools;
8use PVE::Cluster;
9
e6c91e00
TL
10# MA-L (large) assigned by IEEE
11my $PROXMOX_OUI = 'BC:24:11';
12
b466e489
FE
13my $crs_format = {
14 ha => {
15 type => 'string',
16 enum => ['basic', 'static'],
c008170e 17 optional => 1,
b466e489 18 default => 'basic',
c008170e 19 description => "Use this resource scheduler mode for HA.",
b466e489
FE
20 verbose_description => "Configures how the HA manager should select nodes to start or ".
21 "recover services. With 'basic', only the number of services is used, with 'static', ".
22 "static CPU and memory configuration of services is considered.",
23 },
1a75f4db
TL
24 'ha-rebalance-on-start' => {
25 type => 'boolean',
26 optional => 1,
27 default => 0,
28 description => "Set to use CRS for selecting a suited node when a HA services request-state"
29 ." changes from stop to start.",
30 }
b466e489
FE
31};
32
ab966729
FG
33my $migration_format = {
34 type => {
35 default_key => 1,
36 type => 'string',
37 enum => ['secure', 'insecure'],
38 description => "Migration traffic is encrypted using an SSH tunnel by " .
39 "default. On secure, completely private networks this can be " .
40 "disabled to increase performance.",
41 default => 'secure',
42 },
43 network => {
44 optional => 1,
45 type => 'string', format => 'CIDR',
46 format_description => 'CIDR',
47 description => "CIDR of the (sub) network that is used for migration."
48 },
49};
50
63e5cd09
TL
51my $notification_format = {
52 'package-updates' => {
53 type => 'string',
54 enum => ['auto', 'always', 'never'],
996e1e7a
LW
55 description => "DEPRECATED: Use datacenter notification settings instead."
56 ." Control when the daily update job should send out notifications.",
57 verbose_description => "DEPRECATED: Use datacenter notification settings instead.\n"
58 ."Control how often the daily update job should send out notifications:\n"
63e5cd09
TL
59 ."* 'auto' daily for systems with a valid subscription, as those are assumed to be "
60 ." production-ready and thus should know about pending updates.\n"
61 ."* 'always' every update, if there are new pending updates.\n"
62 ."* 'never' never send a notification for new pending updates.\n",
63 default => 'auto',
d91e09cf
LW
64 optional => 1,
65 },
996e1e7a
LW
66 # TODO: These are the left-overs of the first version of the overhauled notification system.
67 # It was already available on pvetest, thus we should keep these entries here for a while to
68 # avoid any breakage.
d91e09cf
LW
69 'target-package-updates' => {
70 type => 'string',
71 format_description => 'TARGET',
996e1e7a 72 description => "UNUSED - Use datacenter notification settings instead.",
d91e09cf
LW
73 optional => 1,
74 },
75 'fencing' => {
76 type => 'string',
77 enum => ['always', 'never'],
996e1e7a 78 description => "UNUSED - Use datacenter notification settings instead.",
d91e09cf
LW
79 optional => 1,
80 },
81 'target-fencing' => {
82 type => 'string',
83 format_description => 'TARGET',
996e1e7a 84 description => "UNUSED - Use datacenter notification settings instead.",
d91e09cf
LW
85 optional => 1,
86 },
87 'replication' => {
88 type => 'string',
89 enum => ['always', 'never'],
996e1e7a 90 description => "UNUSED - Use datacenter notification settings instead.",
d91e09cf
LW
91 optional => 1,
92 },
93 'target-replication' => {
94 type => 'string',
95 format_description => 'TARGET',
996e1e7a 96 description => "UNUSED - Use datacenter notification settings instead.",
d91e09cf 97 optional => 1,
63e5cd09
TL
98 },
99};
100
731f4e15
FE
101register_standard_option('pve-ha-shutdown-policy', {
102 type => 'string',
103 enum => ['freeze', 'failover', 'conditional', 'migrate'],
104 description => "The policy for HA services on node shutdown. 'freeze' disables ".
105 "auto-recovery, 'failover' ensures recovery, 'conditional' recovers on ".
106 "poweroff and freezes on reboot. 'migrate' will migrate running services ".
107 "to other nodes, if possible. With 'freeze' or 'failover', HA Services will ".
108 "always get stopped first on shutdown.",
109 verbose_description => "Describes the policy for handling HA services on poweroff ".
110 "or reboot of a node. Freeze will always freeze services which are still located ".
111 "on the node on shutdown, those services won't be recovered by the HA manager. ".
112 "Failover will not mark the services as frozen and thus the services will get ".
113 "recovered to other nodes, if the shutdown node does not come up again quickly ".
114 "(< 1min). 'conditional' chooses automatically depending on the type of shutdown, ".
115 "i.e., on a reboot the service will be frozen but on a poweroff the service will ".
116 "stay as is, and thus get recovered after about 2 minutes. ".
117 "Migrate will try to move all running services to another node when a reboot or ".
118 "shutdown was triggered. The poweroff process will only continue once no running services ".
119 "are located on the node anymore. If the node comes up again, the service will ".
120 "be moved back to the previously powered-off node, at least if no other migration, ".
121 "reloaction or recovery took place.",
122 default => 'conditional',
123});
124
ab966729 125my $ha_format = {
731f4e15 126 shutdown_policy => get_standard_option('pve-ha-shutdown-policy'),
ab966729
FG
127};
128
75e5d02e
TL
129my $next_id_format = {
130 lower => {
131 type => 'integer',
132 description => "Lower, inclusive boundary for free next-id API range.",
133 min => 100,
134 max => 1000 * 1000 * 1000 - 1,
135 default => 100,
136 optional => 1,
137 },
138 upper => {
139 type => 'integer',
8ca3c4bb 140 description => "Upper, exclusive boundary for free next-id API range.",
75e5d02e 141 min => 100,
8ca3c4bb 142 max => 1000 * 1000 * 1000,
75e5d02e
TL
143 default => 1000 * 1000, # lower than the maximum on purpose
144 optional => 1,
145 },
146};
147
bcfa5ac1 148my $u2f_format = {
ab966729
FG
149 appid => {
150 type => 'string',
151 description => "U2F AppId URL override. Defaults to the origin.",
152 format_description => 'APPID',
153 optional => 1,
154 },
155 origin => {
156 type => 'string',
157 description => "U2F Origin override. Mostly useful for single nodes with a single URL.",
158 format_description => 'URL',
159 optional => 1,
160 },
161};
162
8545a705
WB
163my $webauthn_format = {
164 rp => {
165 type => 'string',
166 description =>
167 'Relying party name. Any text identifier.'
168 .' Changing this *may* break existing credentials.',
169 format_description => 'RELYING_PARTY',
170 optional => 1,
171 },
172 origin => {
173 type => 'string',
174 description =>
175 'Site origin. Must be a `https://` URL (or `http://localhost`).'
176 .' Should contain the address users type in their browsers to access'
177 .' the web interface.'
178 .' Changing this *may* break existing credentials.',
179 format_description => 'URL',
180 optional => 1,
181 },
182 id => {
183 type => 'string',
184 description =>
3b0214b6 185 'Relying party ID. Must be the domain name without protocol, port or location.'
8545a705
WB
186 .' Changing this *will* break existing credentials.',
187 format_description => 'DOMAINNAME',
188 optional => 1,
189 },
3b0214b6
WB
190 'allow-subdomains' => {
191 type => 'boolean',
192 description => 'Whether to allow the origin to be a subdomain, rather than the exact URL.',
193 optional => 1,
194 default => 1,
195 },
8545a705 196};
ab966729
FG
197
198PVE::JSONSchema::register_format('mac-prefix', \&pve_verify_mac_prefix);
199sub pve_verify_mac_prefix {
200 my ($mac_prefix, $noerr) = @_;
201
202 if ($mac_prefix !~ m/^[a-f0-9][02468ace](?::[a-f0-9]{2}){0,2}:?$/i) {
203 return undef if $noerr;
204 die "value is not a valid unicast MAC address prefix\n";
205 }
206 return $mac_prefix;
207}
208
af234c4b
DC
209my $COLOR_RE = '[0-9a-fA-F]{6}';
210my $TAG_COLOR_OVERRIDE_RE = "(?:${PVE::JSONSchema::PVE_TAG_RE}:${COLOR_RE}(?:\:${COLOR_RE})?)";
211
212my $tag_style_format = {
213 'shape' => {
214 optional => 1,
215 type => 'string',
216 enum => ['full', 'circle', 'dense', 'none'],
217 default => 'circle',
218 description => "Tag shape for the web ui tree. 'full' draws the full tag. "
219 ."'circle' draws only a circle with the background color. "
220 ."'dense' only draws a small rectancle (useful when many tags are assigned to each guest)."
221 ."'none' disables showing the tags.",
222 },
223 'color-map' => {
224 optional => 1,
225 type => 'string',
226 pattern => "${TAG_COLOR_OVERRIDE_RE}(?:\;$TAG_COLOR_OVERRIDE_RE)*",
227 typetext => '<tag>:<hex-color>[:<hex-color-for-text>][;<tag>=...]',
228 description => "Manual color mapping for tags (semicolon separated).",
229 },
8456e52e
DC
230 ordering => {
231 optional => 1,
232 type => 'string',
233 enum => ['config', 'alphabetical'],
234 default => 'alphabetical',
2453d13f
TL
235 description => 'Controls the sorting of the tags in the web-interface and the API update.',
236 },
237 'case-sensitive' => {
238 type => 'boolean',
239 description => 'Controls if filtering for unique tags on update should check case-sensitive.',
240 optional => 1,
241 default => 0,
242 },
af234c4b
DC
243};
244
c17e397b
DC
245my $user_tag_privs_format = {
246 'user-allow' => {
247 optional => 1,
248 type => 'string',
249 enum => ['none', 'list', 'existing', 'free'],
250 default => 'free',
c274026b
TL
251 description => "Controls tag usage for users without `Sys.Modify` on `/` by either"
252 ." allowing `none`, a `list`, already `existing` or anything (`free`).",
253 verbose_description => "Controls which tags can be set or deleted on resources a user"
254 ." controls (such as guests). Users with the `Sys.Modify` privilege on `/` are always"
255 ."unrestricted.\n"
256 ."* 'none' no tags are usable.\n"
257 ."* 'list' tags from 'user-allow-list' are usable.\n"
258 ."* 'existing' like list, but already existing tags of resources are also usable.\n"
259 ."* 'free' no tag restrictions.\n",
c17e397b
DC
260 },
261 'user-allow-list' => {
262 optional => 1,
263 type => 'string',
264 pattern => "${PVE::JSONSchema::PVE_TAG_RE}(?:\;${PVE::JSONSchema::PVE_TAG_RE})*",
265 typetext => "<tag>[;<tag>...]",
266 description => "List of tags users are allowed to set and delete (semicolon separated) "
267 ."for 'user-allow' values 'list' and 'existing'.",
268 },
269};
270
ab966729
FG
271my $datacenter_schema = {
272 type => "object",
273 additionalProperties => 0,
274 properties => {
b466e489
FE
275 crs => {
276 optional => 1,
277 type => 'string', format => $crs_format,
278 description => "Cluster resource scheduling settings.",
279 },
ab966729
FG
280 keyboard => {
281 optional => 1,
282 type => 'string',
283 description => "Default keybord layout for vnc server.",
284 enum => PVE::Tools::kvmkeymaplist(),
285 },
286 language => {
287 optional => 1,
288 type => 'string',
289 description => "Default GUI language.",
290 enum => [
a21d7e22
TL
291 'ar', # Arabic
292 'ca', # Catalan
293 'da', # Danish
294 'de', # German
295 'en', # English
296 'es', # Spanish
297 'eu', # Euskera (Basque)
298 'fa', # Persian (Farsi)
299 'fr', # French
300 'hr', # Croatian
301 'he', # Hebrew
302 'it', # Italian
303 'ja', # Japanese
304 'ka', # Georgian
305 'kr', # Korean
306 'nb', # Norwegian (Bokmal)
307 'nl', # Dutch
308 'nn', # Norwegian (Nynorsk)
309 'pl', # Polish
310 'pt_BR', # Portuguese (Brazil)
311 'ru', # Russian
312 'sl', # Slovenian
313 'sv', # Swedish
314 'tr', # Turkish
315 'ukr', # Ukrainian
316 'zh_CN', # Chinese (Simplified)
317 'zh_TW', # Chinese (Traditional)
ab966729
FG
318 ],
319 },
320 http_proxy => {
321 optional => 1,
322 type => 'string',
323 description => "Specify external http proxy which is used for downloads (example: 'http://username:password\@host:port/')",
324 pattern => "http://.*",
325 },
0cc6e737 326 # FIXME: remove with 8.0 (add check to pve7to8!), merged into "migration" since 4.3
ab966729
FG
327 migration_unsecure => {
328 optional => 1,
329 type => 'boolean',
330 description => "Migration is secure using SSH tunnel by default. " .
331 "For secure private networks you can disable it to speed up " .
332 "migration. Deprecated, use the 'migration' property instead!",
333 },
75e5d02e
TL
334 'next-id' => {
335 optional => 1,
336 type => 'string',
337 format => $next_id_format,
338 description => "Control the range for the free VMID auto-selection pool.",
339 },
ab966729
FG
340 migration => {
341 optional => 1,
342 type => 'string', format => $migration_format,
343 description => "For cluster wide migration settings.",
344 },
345 console => {
346 optional => 1,
347 type => 'string',
0cc6e737
TL
348 description => "Select the default Console viewer. You can either use the builtin java"
349 ." applet (VNC; deprecated and maps to html5), an external virt-viewer comtatible application (SPICE), an HTML5 based vnc viewer (noVNC), or an HTML5 based console client (xtermjs). If the selected viewer is not available (e.g. SPICE not activated for the VM), the fallback is noVNC.",
044f5dc6 350 # FIXME: remove 'applet' with 9.0 (add pve8to9 check!)
ab966729
FG
351 enum => ['applet', 'vv', 'html5', 'xtermjs'],
352 },
353 email_from => {
354 optional => 1,
355 type => 'string',
356 format => 'email-opt',
357 description => "Specify email address to send notification from (default is root@\$hostname)",
358 },
359 max_workers => {
360 optional => 1,
361 type => 'integer',
362 minimum => 1,
363 description => "Defines how many workers (per node) are maximal started ".
364 " on actions like 'stopall VMs' or task from the ha-manager.",
365 },
366 fencing => {
367 optional => 1,
368 type => 'string',
369 default => 'watchdog',
370 enum => [ 'watchdog', 'hardware', 'both' ],
371 description => "Set the fencing mode of the HA cluster. Hardware mode " .
372 "needs a valid configuration of fence devices in /etc/pve/ha/fence.cfg." .
373 " With both all two modes are used." .
374 "\n\nWARNING: 'hardware' and 'both' are EXPERIMENTAL & WIP",
375 },
376 ha => {
377 optional => 1,
378 type => 'string', format => $ha_format,
379 description => "Cluster wide HA settings.",
380 },
381 mac_prefix => {
382 optional => 1,
383 type => 'string',
384 format => 'mac-prefix',
e6c91e00 385 default => $PROXMOX_OUI,
f40a6d9c 386 description => "Prefix for the auto-generated MAC addresses of virtual guests. The"
e6c91e00 387 ." default '$PROXMOX_OUI' is the OUI assigned by the IEEE to Proxmox Server Solutions"
f40a6d9c
TL
388 ." GmbH for a 24-bit large MAC block. You're allowed to use this in local networks,"
389 ." i.e., those not directly reachable by the public (e.g., in a LAN or behind NAT)."
390 ,
391 verbose_description => "Prefix for the auto-generated MAC addresses of virtual guests."
392 ." The default `BC:24:11` is the Organizationally Unique Identifier (OUI) assigned"
393 ." by the IEEE to Proxmox Server Solutions GmbH for a MAC Address Block Large (MA-L)."
394 ." You're allowed to use this in local networks, i.e., those not directly reachable"
395 ." by the public (e.g., in a LAN or NAT/Masquerading).\n"
396 ." \nNote that when you run multiple cluster that (partially) share the networks of"
397 ." their virtual guests, it's highly recommended that you extend the default MAC"
398 ." prefix, or generate a custom (valid) one, to reduce the chance of MAC collisions."
399 ." For example, add a separate extra hexadecimal to the Proxmox OUI for each cluster,"
e6c91e00 400 ." like `$PROXMOX_OUI:0` for the first, `$PROXMOX_OUI:1` for the second, and so on.\n"
f40a6d9c
TL
401 ." Alternatively, you can also separate the networks of the guests logically, e.g.,"
402 ." by using VLANs.\n\nFor publicly accessible guests it's recommended that you get"
403 ." your own https://standards.ieee.org/products-programs/regauth/[OUI from the IEEE]"
404 ." registered or coordinate with your, or your hosting providers, network admins."
405 ,
ab966729 406 },
63e5cd09
TL
407 notify => {
408 optional => 1,
409 type => 'string', format => $notification_format,
410 description => "Cluster-wide notification settings.",
411 },
ab966729
FG
412 bwlimit => PVE::JSONSchema::get_standard_option('bwlimit'),
413 u2f => {
414 optional => 1,
415 type => 'string',
416 format => $u2f_format,
417 description => 'u2f',
418 },
8545a705
WB
419 webauthn => {
420 optional => 1,
421 type => 'string',
422 format => $webauthn_format,
423 description => 'webauthn configuration',
424 },
2ae1c0bb
DJ
425 description => {
426 type => 'string',
427 description => "Datacenter description. Shown in the web-interface datacenter notes panel."
428 ." This is saved as comment inside the configuration file.",
429 maxLength => 64 * 1024,
430 optional => 1,
431 },
af234c4b
DC
432 'tag-style' => {
433 optional => 1,
434 type => 'string',
435 description => "Tag style options.",
436 format => $tag_style_format,
437 },
c17e397b
DC
438 'user-tag-access' => {
439 optional => 1,
440 type => 'string',
441 description => "Privilege options for user-settable tags",
442 format => $user_tag_privs_format,
443 },
444 'registered-tags' => {
445 optional => 1,
446 type => 'string',
447 description => "A list of tags that require a `Sys.Modify` on '/' to set and delete. "
448 ."Tags set here that are also in 'user-tag-access' also require `Sys.Modify`.",
449 pattern => "(?:${PVE::JSONSchema::PVE_TAG_RE};)*${PVE::JSONSchema::PVE_TAG_RE}",
450 typetext => "<tag>[;<tag>...]",
451 },
ab966729
FG
452 },
453};
454
455# make schema accessible from outside (for documentation)
456sub get_datacenter_schema { return $datacenter_schema };
457
458sub parse_datacenter_config {
459 my ($filename, $raw) = @_;
460
b5e2b244
TL
461 $raw = '' if !defined($raw);
462
2ae1c0bb
DJ
463 # description may be comment or key-value pair (or both)
464 my $comment = '';
459f6084 465 for my $line (split(/\n/, $raw)) {
09b99550 466 if ($line =~ /^\#(.*)$/) {
2ae1c0bb
DJ
467 $comment .= PVE::Tools::decode_text($1) . "\n";
468 }
469 }
470
471 # parse_config ignores lines with # => use $raw
b5e2b244 472 my $res = PVE::JSONSchema::parse_config($datacenter_schema, $filename, $raw);
ab966729 473
2ae1c0bb
DJ
474 $res->{description} = $comment;
475
f40a6d9c
TL
476 # it could be better to track that this is the default, and not explicitly set, but having
477 # no MAC prefix is really not ideal, and overriding that here centrally catches all call sites
e6c91e00 478 $res->{mac_prefix} = $PROXMOX_OUI if !defined($res->{mac_prefix});
f40a6d9c 479
b466e489
FE
480 if (my $crs = $res->{crs}) {
481 $res->{crs} = parse_property_string($crs_format, $crs);
482 }
483
ab966729 484 if (my $migration = $res->{migration}) {
51866274 485 $res->{migration} = parse_property_string($migration_format, $migration);
ab966729
FG
486 }
487
75e5d02e
TL
488 if (my $next_id = $res->{'next-id'}) {
489 $res->{'next-id'} = parse_property_string($next_id_format, $next_id);
490 }
491
ab966729 492 if (my $ha = $res->{ha}) {
51866274 493 $res->{ha} = parse_property_string($ha_format, $ha);
ab966729 494 }
63e5cd09
TL
495 if (my $notify = $res->{notify}) {
496 $res->{notify} = parse_property_string($notification_format, $notify);
497 }
ab966729 498
bcfa5ac1 499 if (my $u2f = $res->{u2f}) {
51866274 500 $res->{u2f} = parse_property_string($u2f_format, $u2f);
bcfa5ac1
FG
501 }
502
8545a705 503 if (my $webauthn = $res->{webauthn}) {
51866274 504 $res->{webauthn} = parse_property_string($webauthn_format, $webauthn);
8545a705
WB
505 }
506
af234c4b
DC
507 if (my $tag_style = $res->{'tag-style'}) {
508 $res->{'tag-style'} = parse_property_string($tag_style_format, $tag_style);
509 }
510
c17e397b
DC
511 if (my $user_tag_privs = $res->{'user-tag-access'}) {
512 $res->{'user-tag-access'} =
513 parse_property_string($user_tag_privs_format, $user_tag_privs);
514
515 if (my $user_tags = $res->{'user-tag-access'}->{'user-allow-list'}) {
516 $res->{'user-tag-access'}->{'user-allow-list'} = [split(';', $user_tags)];
517 }
518 }
519
520 if (my $admin_tags = $res->{'registered-tags'}) {
521 $res->{'registered-tags'} = [split(';', $admin_tags)];
522 }
523
ab966729
FG
524 # for backwards compatibility only, new migration property has precedence
525 if (defined($res->{migration_unsecure})) {
526 if (defined($res->{migration}->{type})) {
527 warn "deprecated setting 'migration_unsecure' and new 'migration: type' " .
528 "set at same time! Ignore 'migration_unsecure'\n";
529 } else {
530 $res->{migration}->{type} = ($res->{migration_unsecure}) ? 'insecure' : 'secure';
531 }
532 }
533
534 # for backwards compatibility only, applet maps to html5
535 if (defined($res->{console}) && $res->{console} eq 'applet') {
536 $res->{console} = 'html5';
537 }
538
539 return $res;
540}
541
542sub write_datacenter_config {
543 my ($filename, $cfg) = @_;
544
545 # map deprecated setting to new one
546 if (defined($cfg->{migration_unsecure}) && !defined($cfg->{migration})) {
547 my $migration_unsecure = delete $cfg->{migration_unsecure};
548 $cfg->{migration}->{type} = ($migration_unsecure) ? 'insecure' : 'secure';
549 }
550
551 # map deprecated applet setting to html5
552 if (defined($cfg->{console}) && $cfg->{console} eq 'applet') {
553 $cfg->{console} = 'html5';
554 }
555
e6c91e00
TL
556 if (defined($cfg->{mac_prefix}) && uc($cfg->{mac_prefix}) eq $PROXMOX_OUI) {
557 delete $cfg->{mac_prefix}; # avoid writing out default prefix
558 }
559
b466e489
FE
560 if (ref(my $crs = $cfg->{crs})) {
561 $cfg->{crs} = PVE::JSONSchema::print_property_string($crs, $crs_format);
562 }
563
5b576e26 564 if (ref(my $migration = $cfg->{migration})) {
ab966729
FG
565 $cfg->{migration} = PVE::JSONSchema::print_property_string($migration, $migration_format);
566 }
567
75e5d02e
TL
568 if (defined(my $next_id = $cfg->{'next-id'})) {
569 $next_id = parse_property_string($next_id_format, $next_id) if !ref($next_id);
570
571 my $lower = int($next_id->{lower} // $next_id_format->{lower}->{default});
572 my $upper = int($next_id->{upper} // $next_id_format->{upper}->{default});
573
fe2bc758 574 die "lower ($lower) <= upper ($upper) boundary rule broken\n" if $lower > $upper;
75e5d02e
TL
575
576 $cfg->{'next-id'} = PVE::JSONSchema::print_property_string($next_id, $next_id_format);
577 }
578
5b576e26 579 if (ref(my $ha = $cfg->{ha})) {
ab966729
FG
580 $cfg->{ha} = PVE::JSONSchema::print_property_string($ha, $ha_format);
581 }
63e5cd09
TL
582 if (ref(my $notify = $cfg->{notify})) {
583 $cfg->{notify} = PVE::JSONSchema::print_property_string($notify, $notification_format);
584 }
ab966729 585
5b576e26 586 if (ref(my $u2f = $cfg->{u2f})) {
bcfa5ac1
FG
587 $cfg->{u2f} = PVE::JSONSchema::print_property_string($u2f, $u2f_format);
588 }
589
5b576e26 590 if (ref(my $webauthn = $cfg->{webauthn})) {
8545a705
WB
591 $cfg->{webauthn} = PVE::JSONSchema::print_property_string($webauthn, $webauthn_format);
592 }
593
af234c4b
DC
594 if (ref(my $tag_style = $cfg->{'tag-style'})) {
595 $cfg->{'tag-style'} = PVE::JSONSchema::print_property_string($tag_style, $tag_style_format);
596 }
597
c17e397b
DC
598 if (ref(my $user_tag_privs = $cfg->{'user-tag-access'})) {
599 if (my $user_tags = $user_tag_privs->{'user-allow-list'}) {
600 $user_tag_privs->{'user-allow-list'} = join(';', sort $user_tags->@*);
601 }
602 $cfg->{'user-tag-access'} =
603 PVE::JSONSchema::print_property_string($user_tag_privs, $user_tag_privs_format);
604 }
605
606 if (ref(my $admin_tags = $cfg->{'registered-tags'})) {
607 $cfg->{'registered-tags'} = join(';', sort $admin_tags->@*);
608 }
609
2ae1c0bb
DJ
610 my $comment = '';
611 # add description as comment to top of file
612 my $description = $cfg->{description} || '';
613 foreach my $line (split(/\n/, $description)) {
614 $comment .= '#' . PVE::Tools::encode_text($line) . "\n";
615 }
616 delete $cfg->{description}; # add only as comment, no additional key-value pair
617 my $dump = PVE::JSONSchema::dump_config($datacenter_schema, $filename, $cfg);
618
619 return $comment . "\n" . $dump;
ab966729
FG
620}
621
02ce710f
TL
622PVE::Cluster::cfs_register_file(
623 'datacenter.cfg',
624 \&parse_datacenter_config,
625 \&write_datacenter_config,
626);
ab966729
FG
627
6281;