]> git.proxmox.com Git - pve-manager.git/blob - PVE/NodeConfig.pm
update shipped appliance info index
[pve-manager.git] / PVE / NodeConfig.pm
1 package PVE::NodeConfig;
2
3 use strict;
4 use warnings;
5
6 use PVE::CertHelpers;
7 use PVE::JSONSchema qw(get_standard_option);
8 use PVE::Tools qw(file_get_contents file_set_contents lock_file);
9
10 my $node_config_lock = '/var/lock/pvenode.lock';
11
12 PVE::JSONSchema::register_format('pve-acme-domain', sub {
13 my ($domain, $noerr) = @_;
14
15 my $label = qr/[a-z0-9][a-z0-9_-]*/i;
16
17 return $domain if $domain =~ /^$label(?:\.$label)+$/;
18 return undef if $noerr;
19 die "value does not look like a valid domain name";
20 });
21
22 sub config_file {
23 my ($node) = @_;
24
25 return "/etc/pve/nodes/${node}/config";
26 }
27
28 sub load_config {
29 my ($node) = @_;
30
31 my $filename = config_file($node);
32 my $raw = eval { PVE::Tools::file_get_contents($filename); };
33 return {} if !$raw;
34
35 return parse_node_config($raw);
36 }
37
38 sub write_config {
39 my ($node, $conf) = @_;
40
41 my $filename = config_file($node);
42
43 my $raw = write_node_config($conf);
44
45 PVE::Tools::file_set_contents($filename, $raw);
46 }
47
48 sub lock_config {
49 my ($node, $code, @param) = @_;
50
51 my $res = lock_file($node_config_lock, 10, $code, @param);
52
53 die $@ if $@;
54
55 return $res;
56 }
57
58 my $confdesc = {
59 description => {
60 type => 'string',
61 description => 'Node description/comment.',
62 optional => 1,
63 },
64 wakeonlan => {
65 type => 'string',
66 description => 'MAC address for wake on LAN',
67 format => 'mac-addr',
68 optional => 1,
69 },
70 };
71
72 my $acmedesc = {
73 account => get_standard_option('pve-acme-account-name'),
74 domains => {
75 type => 'string',
76 format => 'pve-acme-domain-list',
77 format_description => 'domain[;domain;...]',
78 description => 'List of domains for this node\'s ACME certificate',
79 },
80 };
81 PVE::JSONSchema::register_format('pve-acme-node-conf', $acmedesc);
82
83 $confdesc->{acme} = {
84 type => 'string',
85 description => 'Node specific ACME settings.',
86 format => $acmedesc,
87 optional => 1,
88 };
89
90 sub check_type {
91 my ($key, $value) = @_;
92
93 die "unknown setting '$key'\n" if !$confdesc->{$key};
94
95 my $type = $confdesc->{$key}->{type};
96
97 if (!defined($value)) {
98 die "got undefined value\n";
99 }
100
101 if ($value =~ m/[\n\r]/) {
102 die "property contains a line feed\n";
103 }
104
105 if ($type eq 'boolean') {
106 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
107 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
108 die "type check ('boolean') failed - got '$value'\n";
109 } elsif ($type eq 'integer') {
110 return int($1) if $value =~ m/^(\d+)$/;
111 die "type check ('integer') failed - got '$value'\n";
112 } elsif ($type eq 'number') {
113 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
114 die "type check ('number') failed - got '$value'\n";
115 } elsif ($type eq 'string') {
116 if (my $fmt = $confdesc->{$key}->{format}) {
117 PVE::JSONSchema::check_format($fmt, $value);
118 return $value;
119 } elsif (my $pattern = $confdesc->{$key}->{pattern}) {
120 if ($value !~ m/^$pattern$/) {
121 die "value does not match the regex pattern\n";
122 }
123 }
124 return $value;
125 } else {
126 die "internal error"
127 }
128 }
129
130 sub parse_node_config {
131 my ($content) = @_;
132
133 return undef if !defined($content);
134
135 my $conf = {
136 digest => Digest::SHA::sha1_hex($content),
137 };
138 my $descr = '';
139
140 my @lines = split(/\n/, $content);
141 foreach my $line (@lines) {
142 if ($line =~ /^\#(.*)\s*$/ || $line =~ /^description:\s*(.*\S)\s*$/) {
143 $descr .= PVE::Tools::decode_text($1) . "\n";
144 next;
145 }
146 if ($line =~ /^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
147 my $key = $1;
148 my $value = $2;
149 eval { $value = check_type($key, $value); };
150 warn "cannot parse value of '$key' in node config: $@" if $@;
151 $conf->{$key} = $value;
152 } else {
153 warn "cannot parse line '$line' in node config\n";
154 }
155 }
156
157 $conf->{description} = $descr if $descr;
158
159 return $conf;
160 }
161
162 sub write_node_config {
163 my ($conf) = @_;
164
165 my $raw = '';
166 # add description as comment to top of file
167 my $descr = $conf->{description} || '';
168 foreach my $cl (split(/\n/, $descr)) {
169 $raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
170 }
171
172 for my $key (sort keys %$conf) {
173 next if ($key eq 'description');
174 next if ($key eq 'digest');
175
176 my $value = $conf->{$key};
177 die "detected invalid newline inside property '$key'\n"
178 if $value =~ m/\n/;
179 $raw .= "$key: $value\n";
180 }
181
182 return $raw;
183 }
184
185 sub parse_acme {
186 my ($data, $noerr) = @_;
187
188 $data //= '';
189
190 my $res = eval { PVE::JSONSchema::parse_property_string($acmedesc, $data); };
191 if ($@) {
192 return undef if $noerr;
193 die $@;
194 }
195
196 $res->{domains} = [ PVE::Tools::split_list($res->{domains}) ];
197
198 return $res;
199 }
200
201 sub print_acme {
202 my ($acme) = @_;
203
204 $acme->{domains} = join(';', $acme->{domains}) if $acme->{domains};
205 return PVE::JSONSchema::print_property_string($acme, $acmedesc);
206 }
207
208 sub get_nodeconfig_schema {
209 return $confdesc;
210 }
211
212 1;