]>
git.proxmox.com Git - pve-manager.git/blob - PVE/NodeConfig.pm
1 package PVE
::NodeConfig
;
7 use PVE
::JSONSchema
qw(get_standard_option);
8 use PVE
::Tools
qw(file_get_contents file_set_contents lock_file);
11 use PVE
::API2
::ACMEPlugin
;
13 # register up to 5 domain names per node for now
16 my $node_config_lock = '/var/lock/pvenode.lock';
18 PVE
::JSONSchema
::register_format
('pve-acme-domain', sub {
19 my ($domain, $noerr) = @_;
21 my $label = qr/[a-z0-9][a-z0-9_-]*/i;
23 return $domain if $domain =~ /^$label(?:\.$label)+$/;
24 return undef if $noerr;
25 die "value '$domain' does not look like a valid domain name!\n";
28 PVE
::JSONSchema
::register_format
('pve-acme-alias', sub {
29 my ($alias, $noerr) = @_;
31 my $label = qr/[a-z0-9_][a-z0-9_-]*/i;
33 return $alias if $alias =~ /^$label(?:\.$label)+$/;
34 return undef if $noerr;
35 die "value '$alias' does not look like a valid alias name!\n";
41 return "/etc/pve/nodes/${node}/config";
47 my $filename = config_file
($node);
48 my $raw = eval { PVE
::Tools
::file_get_contents
($filename); };
51 return parse_node_config
($raw, $filename);
55 my ($node, $conf) = @_;
57 my $filename = config_file
($node);
59 my $raw = write_node_config
($conf);
61 PVE
::Tools
::file_set_contents
($filename, $raw);
65 my ($node, $realcode, @param) = @_;
67 # make sure configuration file is up-to-date
69 PVE
::Cluster
::cfs_update
();
73 my $res = lock_file
($node_config_lock, 10, $code, @param);
83 description
=> "Description for the Node. Shown in the web-interface node notes panel."
84 ." This is saved as comment inside the configuration file.",
85 maxLength
=> 64 * 1024,
90 description
=> 'MAC address for wake on LAN',
94 'startall-onboot-delay' => {
95 description
=> 'Initial delay in seconds, before starting all the Virtual Guests with on-boot enabled.',
104 my $acme_domain_desc = {
107 format
=> 'pve-acme-domain',
108 format_description
=> 'domain',
109 description
=> 'domain for this node\'s ACME certificate',
114 format
=> 'pve-configid',
115 description
=> 'The ACME plugin ID',
116 format_description
=> 'name of the plugin configuration',
118 default => 'standalone',
122 format
=> 'pve-acme-alias',
123 format_description
=> 'domain',
124 description
=> 'Alias for the Domain to verify ACME Challenge over DNS',
130 account
=> get_standard_option
('pve-acme-account-name'),
133 format
=> 'pve-acme-domain-list',
134 format_description
=> 'domain[;domain;...]',
135 description
=> 'List of domains for this node\'s ACME certificate',
140 $confdesc->{acme
} = {
142 description
=> 'Node specific ACME settings.',
147 for my $i (0..$MAXDOMAINS) {
148 $confdesc->{"acmedomain$i"} = {
150 description
=> 'ACME domain and validation plugin',
151 format
=> $acme_domain_desc,
158 properties
=> $confdesc,
161 sub parse_node_config
: prototype($$) {
162 my ($content, $filename) = @_;
164 return undef if !defined($content);
165 my $digest = Digest
::SHA
::sha1_hex
($content);
167 my $conf = PVE
::JSONSchema
::parse_config
($conf_schema, $filename, $content, 'description');
168 $conf->{digest
} = $digest;
173 sub write_node_config
{
177 # add description as comment to top of file
178 my $descr = $conf->{description
} || '';
179 foreach my $cl (split(/\n/, $descr)) {
180 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
183 for my $key (sort keys %$conf) {
184 next if ($key eq 'description');
185 next if ($key eq 'digest');
187 my $value = $conf->{$key};
188 die "detected invalid newline inside property '$key'\n"
190 $raw .= "$key: $value\n";
196 # we always convert domain values to lower case, since DNS entries are not case
197 # sensitive and ACME implementations might convert the ordered identifiers
200 my ($node_conf, $noerr) = @_;
205 if (defined($node_conf->{acme
})) {
207 PVE
::JSONSchema
::parse_property_string
($acmedesc, $node_conf->{acme
})
210 return undef if $noerr;
213 my $standalone_domains = delete($res->{domains
}) // '';
214 $res->{domains
} = {};
215 for my $domain (split(";", $standalone_domains)) {
216 $domain = lc($domain);
217 die "duplicate domain '$domain' in ACME config properties\n"
218 if defined($res->{domains
}->{$domain});
220 $res->{domains
}->{$domain}->{plugin
} = 'standalone';
221 $res->{domains
}->{$domain}->{_configkey
} = 'acme';
225 $res->{account
} //= 'default';
227 for my $index (0..$MAXDOMAINS) {
228 my $domain_rec = $node_conf->{"acmedomain$index"};
229 next if !defined($domain_rec);
232 PVE
::JSONSchema
::parse_property_string
($acme_domain_desc, $domain_rec)
235 return undef if $noerr;
238 my $domain = lc(delete $parsed->{domain
});
239 if (my $exists = $res->{domains
}->{$domain}) {
240 return undef if $noerr;
241 die "duplicate domain '$domain' in ACME config properties"
242 ." 'acmedomain$index' and '$exists->{_configkey}'\n";
244 $parsed->{plugin
} //= 'standalone';
246 my $plugin_id = $parsed->{plugin
};
247 if ($plugin_id ne 'standalone') {
248 my $plugins = PVE
::API2
::ACMEPlugin
::load_config
();
249 die "plugin '$plugin_id' for domain '$domain' not found!\n"
250 if !$plugins->{ids
}->{$plugin_id};
253 $parsed->{_configkey
} = "acmedomain$index";
254 $res->{domains
}->{$domain} = $parsed;
260 # expects that basic format verification was already done, this is more higher
263 my ($node_conf) = @_;
265 # verify ACME domain uniqueness
266 my $tmp = get_acme_conf
($node_conf);
273 sub get_nodeconfig_schema
{