]>
Commit | Line | Data |
---|---|---|
c4f78bb7 FG |
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 | ||
4dab82e3 | 15 | my $label = qr/[a-z0-9][a-z0-9_-]*/i; |
c4f78bb7 FG |
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 | }; | |
65 | ||
66 | my $acmedesc = { | |
67 | account => get_standard_option('pve-acme-account-name'), | |
68 | domains => { | |
69 | type => 'string', | |
70 | format => 'pve-acme-domain-list', | |
71 | format_description => 'domain[;domain;...]', | |
72 | description => 'List of domains for this node\'s ACME certificate', | |
73 | }, | |
74 | }; | |
75 | PVE::JSONSchema::register_format('pve-acme-node-conf', $acmedesc); | |
76 | ||
77 | $confdesc->{acme} = { | |
78 | type => 'string', | |
79 | description => 'Node specific ACME settings.', | |
80 | format => $acmedesc, | |
81 | optional => 1, | |
82 | }; | |
83 | ||
84 | sub check_type { | |
85 | my ($key, $value) = @_; | |
86 | ||
87 | die "unknown setting '$key'\n" if !$confdesc->{$key}; | |
88 | ||
89 | my $type = $confdesc->{$key}->{type}; | |
90 | ||
91 | if (!defined($value)) { | |
92 | die "got undefined value\n"; | |
93 | } | |
94 | ||
95 | if ($value =~ m/[\n\r]/) { | |
96 | die "property contains a line feed\n"; | |
97 | } | |
98 | ||
99 | if ($type eq 'boolean') { | |
100 | return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i); | |
101 | return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i); | |
102 | die "type check ('boolean') failed - got '$value'\n"; | |
103 | } elsif ($type eq 'integer') { | |
104 | return int($1) if $value =~ m/^(\d+)$/; | |
105 | die "type check ('integer') failed - got '$value'\n"; | |
106 | } elsif ($type eq 'number') { | |
107 | return $value if $value =~ m/^(\d+)(\.\d+)?$/; | |
108 | die "type check ('number') failed - got '$value'\n"; | |
109 | } elsif ($type eq 'string') { | |
110 | if (my $fmt = $confdesc->{$key}->{format}) { | |
111 | PVE::JSONSchema::check_format($fmt, $value); | |
112 | return $value; | |
113 | } | |
114 | return $value; | |
115 | } else { | |
116 | die "internal error" | |
117 | } | |
118 | } | |
119 | sub parse_node_config { | |
120 | my ($content) = @_; | |
121 | ||
122 | return undef if !defined($content); | |
123 | ||
124 | my $conf = { | |
125 | digest => Digest::SHA::sha1_hex($content), | |
126 | }; | |
127 | my $descr = ''; | |
128 | ||
129 | my @lines = split(/\n/, $content); | |
130 | foreach my $line (@lines) { | |
131 | if ($line =~ /^\#(.*)\s*$/) { | |
132 | $descr .= PVE::Tools::decode_text($1) . "\n"; | |
133 | next; | |
134 | } | |
135 | if ($line =~ /^description:\s*(.*\S)\s*$/) { | |
136 | $descr .= PVE::Tools::decode_text($1) . "\n"; | |
137 | next; | |
138 | } | |
139 | if ($line =~ /^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) { | |
140 | my $key = $1; | |
141 | my $value = $2; | |
142 | eval { $value = check_type($key, $value); }; | |
143 | warn "cannot parse value of '$key' in node config: $@" if $@; | |
144 | $conf->{$key} = $value; | |
145 | } else { | |
146 | warn "cannot parse line '$line' in node config\n"; | |
147 | } | |
148 | } | |
149 | ||
150 | $conf->{description} = $descr if $descr; | |
151 | ||
152 | return $conf; | |
153 | } | |
154 | ||
155 | sub write_node_config { | |
156 | my ($conf) = @_; | |
157 | ||
158 | my $raw = ''; | |
159 | # add description as comment to top of file | |
160 | my $descr = $conf->{description} || ''; | |
161 | foreach my $cl (split(/\n/, $descr)) { | |
162 | $raw .= '#' . PVE::Tools::encode_text($cl) . "\n"; | |
163 | } | |
164 | ||
165 | for my $key (sort keys %$conf) { | |
166 | next if ($key eq 'description'); | |
167 | next if ($key eq 'digest'); | |
168 | ||
169 | my $value = $conf->{$key}; | |
170 | die "detected invalid newline inside property '$key'\n" | |
171 | if $value =~ m/\n/; | |
172 | $raw .= "$key: $value\n"; | |
173 | } | |
174 | ||
175 | return $raw; | |
176 | } | |
177 | ||
178 | sub parse_acme { | |
179 | my ($data, $noerr) = @_; | |
180 | ||
181 | $data //= ''; | |
182 | ||
183 | my $res = eval { PVE::JSONSchema::parse_property_string($acmedesc, $data); }; | |
184 | if ($@) { | |
185 | return undef if $noerr; | |
186 | die $@; | |
187 | } | |
188 | ||
189 | $res->{domains} = [ PVE::Tools::split_list($res->{domains}) ]; | |
190 | ||
191 | return $res; | |
192 | } | |
193 | ||
194 | sub print_acme { | |
195 | my ($acme) = @_; | |
196 | ||
197 | $acme->{domains} = join(';', $acme->{domains}) if $acme->{domains}; | |
198 | return PVE::JSONSchema::print_property_string($acme, $acmedesc); | |
199 | } | |
200 | ||
201 | sub get_nodeconfig_schema { | |
202 | return $confdesc; | |
203 | } | |
204 | ||
205 | 1; |